From 578976dcd1db7a5e1a715cde95088d4326c64502 Mon Sep 17 00:00:00 2001 From: Catherine Holloway Date: Fri, 19 Aug 2016 21:26:49 -0700 Subject: [PATCH 1/4] added surelock lm laser --- instruments/ondax/__init__.py | 7 + instruments/ondax/lm.py | 382 +++++++++++++++++++++ instruments/tests/test_ondax/test_lm.py | 428 ++++++++++++++++++++++++ 3 files changed, 817 insertions(+) create mode 100644 instruments/ondax/__init__.py create mode 100644 instruments/ondax/lm.py create mode 100644 instruments/tests/test_ondax/test_lm.py 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..cac0fef63 --- /dev/null +++ b/instruments/ondax/lm.py @@ -0,0 +1,382 @@ +#!/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 + +# CLASSES ##################################################################### + + +class LM(Instrument): + """ + The LM is the Ondax SureLock VHG-stabilized laser diode. + + The user manual can be found here: + 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) + + # ENUMS # + class Status(IntEnum): + """ + Enum containing the 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 relatde to the laser diode's automatic current + control driver + """ + def __init__(self, parent): + self.parent = parent + + @property + def target(self): + """ + The ACC target current + :return: Current ACC of the Laser + :rtype: quantities.mA + """ + response = float(self.parent.query("rstli?")) + return response*pq.mA + + def enable(self): + """ + Enable the ACC driver + """ + self.parent.sendcmd("lcen") + + def disable(self): + """ + Disable the ACC driver + """ + self.parent.sendcmd("lcdis") + + def on(self): + """ + Turn on the ACC driver + """ + self.parent.sendcmd("lcon") + + def off(self): + """ + Turn off the ACC driver + """ + self.parent.sendcmd("lcoff") + + class AutomaticPowerControl(object): + """ + Options and functions related to the laser diode's automatic power + control driver. + """ + def __init__(self, parent): + self.parent = parent + + @property + def target(self): + """ + Return the target laser power in mW + :return: the target laser power + :rtype: quantities.mW + """ + response = self.parent.query("rslp?") + return float(response)*pq.mW + + def enable(self): + """ + Enable the APC driver + """ + self.parent.sendcmd("len") + + def disable(self): + """ + Disable the APC driver + :return: + """ + self.parent.sendcmd("ldis") + + def start(self): + """ + Start the APC scan + :return: + """ + self.parent.sendcmd("sps") + + def stop(self): + """ + Stop the APC scan + :return: + """ + self.parent.sendcmd("cps") + + class Modulation(object): + """ + Options and functions related to the laser's optical output modulation. + """ + def __init__(self, parent): + self.parent = parent + + @property + def on_time(self): + """ + Get the on time of TTL modulation, in mS + :return: the on time in the TTL modulation. + :rtype: quantities.mS + """ + response = self.parent.query("stsont?") + return float(response)*pq.mS + + @on_time.setter + def on_time(self, newval): + """ + Set the on time og TTL modulation, in mS. + :param newval: the modulation on time, in mS. + :type newval: quantities.mS + """ + newval = newval.rescale('mS').magnitude + self.parent.sendcmd("stsont:"+str(newval)) + + @property + def off_time(self): + """ + Get the off time of TTL modulation, in mS + :return: the off time in the TTL modulation. + :rtype: quantities.mS + """ + response = self.parent.query("stsofft?") + return float(response)*pq.mS + + @off_time.setter + def off_time(self, newval): + """ + Set the off time og TTL modulation, in mS. + :param newval: the modulation off time, in mS. + :type newval: quantities.mS + """ + newval = newval.rescale('mS').magnitude + self.parent.sendcmd("stsofft:"+str(newval)) + + def start(self): + """ + Start TTL modulation. + """ + self.parent.sendcmd("stm") + + def stop(self): + """ + Stop TTl modulation. + """ + self.parent.sendcmd("ctm") + + class ThermoElectricCooler(object): + """ + Options and functions relating to the laser diode's thermo electric + cooler. + """ + def __init__(self, parent): + self.parent = parent + + @property + def current(self): + """ + Get the current TEC current + :return: the TEC current + :rtype: quantities.mA + """ + response = self.parent.query("rti?") + return float(response)*pq.mA + + @property + def target(self): + """ + Return the TEC target temperature + :return: The target temperature + :rtype: quantities.degC + """ + response = self.parent.query("rstt?") + return float(response)*pq.degC + + def enable(self): + """ + Enable TEC control + """ + self.parent.sendcmd("tecon") + + def shutdown(self): + """ + Shut down TEC control + """ + self.parent.sendcmd("tecoff") + + def _ack_expected(self, msg=""): + if msg.find("?") > 0: + return None + else: + return "OK" + + @property + def firmware(self): + """ + Return the laser system firmware version. + :return: The laser system firmware. + :rtype: str + """ + response = self.query("rsv?") + return response + + @property + def current(self): + """ + The laser diode current, in mA. + :return: The laser diode current. + :rtype: pq.mA + """ + response = self.query("rli?") + return float(response)*pq.mA + + @current.setter + def current(self, newval): + """ + Set the laser diode current. + :param newval: the + """ + newval = newval.rescale('mA').magnitude + self.sendcmd("slc:"+str(newval)) + + @property + def maximum_current(self): + """ + Read the maximum laser diode current in mA. + :return: The maximum laser diode current. + :rtype: pq.mA + """ + response = self.query("rlcm?") + return float(response)*pq.mA + + @maximum_current.setter + def maximum_current(self, newval): + """ + Set the maximum laser diode current in + mA, if the current is over the limit the laser + will shut down. + + :param newval: the new current. + :type newval: quantities.mA + """ + newval = newval.rescale("mA").magnitude + self.sendcmd("smlc:" + str(newval)) + + @property + def power(self): + """ + The laser's optical power. + :return: the laser power, in mW + :rtype: quantities.mW + """ + response = self.query("rlp?") + return float(response)*pq.mW + + @power.setter + def power(self, newval): + """ + Set the optical power. + :param newval: the new power, in mW. + :type newval: quantities.mW + """ + newval = newval.rescale('mW').magnitude + self.sendcmd("slp:"+str(newval)) + + @property + def serial_number(self): + """ + Return the laser controller serial number + :return: the serial number, as a string. + :rtype: str + """ + response = self.query("rsn?") + return response + + @property + def status(self): + """ + Read laser controller run status. + :return: The status. + :rtype: LM.Status + """ + response = self.query("rlrs?") + return self.Status(int(response)) + + @property + def temperature(self): + """ + Get the current laser diode temperature. + :return: The current laser diode temperature. + :rtype: quantity.degC + """ + response = self.query("rtt?") + return float(response)*pq.degC + + @temperature.setter + def temperature(self, newval): + """ + Set the laser diode temperature + :param newval: the new temperature. + :type newval: quantities.degC + """ + newval = convert_temperature(newval, pq.degC).magnitude + self.sendcmd("stt:"+str(newval)) + + def enable(self): + """ + Enable laser emission. + """ + self.sendcmd("lon") + + def disable(self): + """ + Disable laser emission. + """ + self.sendcmd("loff") + + 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..2df050540 --- /dev/null +++ b/instruments/tests/test_ondax/test_lm.py @@ -0,0 +1,428 @@ +import quantities + +from instruments import ondax +from instruments.tests import expected_protocol + + +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.enable() + + +def test_acc_disable(): + with expected_protocol( + ondax.LM, + [ + "lcdis" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.acc.disable() + + +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.enable() + + +def test_apc_disable(): + with expected_protocol( + ondax.LM, + [ + "ldis" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.apc.disable() + + +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_start(): + with expected_protocol( + ondax.LM, + [ + "stm" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.modulation.start() + + +def test_modulation_stop(): + with expected_protocol( + ondax.LM, + [ + "ctm" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.modulation.stop() + + +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.enable() + + +def test_tec_shutdown(): + with expected_protocol( + ondax.LM, + [ + "tecoff" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.tec.shutdown() + + +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.enable() + + +def test_disable(): + with expected_protocol( + ondax.LM, + [ + "loff" + ], + [ + "OK" + ], + sep="\r" + ) as lm: + lm.disable() + + +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() From 1100d74868efcef1a37d90ef61af84906fcb40dc Mon Sep 17 00:00:00 2001 From: Catherine Holloway Date: Sat, 27 Aug 2016 11:24:40 -0700 Subject: [PATCH 2/4] addressing PR comments --- instruments/ondax/lm.py | 249 ++++++++++++------------ instruments/tests/test_ondax/test_lm.py | 34 ++-- 2 files changed, 146 insertions(+), 137 deletions(-) diff --git a/instruments/ondax/lm.py b/instruments/ondax/lm.py index cac0fef63..da20dadfb 100644 --- a/instruments/ondax/lm.py +++ b/instruments/ondax/lm.py @@ -16,7 +16,8 @@ import quantities as pq from instruments.abstract_instruments import Instrument -from instruments.util_fns import convert_temperature +from instruments.util_fns import convert_temperature, assume_units + # CLASSES ##################################################################### @@ -32,10 +33,11 @@ class LM(Instrument): 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.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): @@ -53,191 +55,203 @@ class Status(IntEnum): # INNER CLASSES # - class AutomaticCurrentControl(object): + class _AutomaticCurrentControl(object): """ - Options and functions relatde to the laser diode's automatic current + Options and functions related to the laser diode's automatic current control driver """ def __init__(self, parent): - self.parent = parent + self._parent = parent + self._enabled = None @property def target(self): """ The ACC target current :return: Current ACC of the Laser - :rtype: quantities.mA + :rtype: ~quantities.Quantity """ - response = float(self.parent.query("rstli?")) + response = float(self._parent.query("rstli?")) return response*pq.mA - def enable(self): - """ - Enable the ACC driver + @property + def enabled(self): """ - self.parent.sendcmd("lcen") + Get/Set the enabled state of the ACC driver - def disable(self): + :return: The enabled state of the ACC driver + :rtype: bool """ - Disable the ACC driver - """ - self.parent.sendcmd("lcdis") + return self._enabled + + @enabled.setter + def enabled(self, newval): + if newval: + self._parent.sendcmd("lcen") + else: + self._parent.sendcmd("lcdis") + self._enabled = newval def on(self): """ Turn on the ACC driver """ - self.parent.sendcmd("lcon") + self._parent.sendcmd("lcon") def off(self): """ Turn off the ACC driver """ - self.parent.sendcmd("lcoff") + self._parent.sendcmd("lcoff") - class AutomaticPowerControl(object): + class _AutomaticPowerControl(object): """ Options and functions related to the laser diode's automatic power control driver. """ def __init__(self, parent): - self.parent = parent + self._parent = parent + self._enabled = None @property def target(self): """ Return the target laser power in mW :return: the target laser power - :rtype: quantities.mW + :rtype: ~quantities.Quantities """ - response = self.parent.query("rslp?") + response = self._parent.query("rslp?") return float(response)*pq.mW - def enable(self): + @property + def enabled(self): """ - Enable the APC driver + Get/Set the enabled state of the APC driver + :return: The enabled state of the APC driver + :rtype: bool """ - self.parent.sendcmd("len") + return self._enabled - def disable(self): - """ - Disable the APC driver - :return: - """ - self.parent.sendcmd("ldis") + @enabled.setter + def enabled(self, newval): + if newval: + self._parent.sendcmd("len") + else: + self._parent.sendcmd("ldis") + self._enabled = newval def start(self): """ - Start the APC scan - :return: + Start the APC scan. """ - self.parent.sendcmd("sps") + self._parent.sendcmd("sps") def stop(self): """ - Stop the APC scan - :return: + Stop the APC scan. """ - self.parent.sendcmd("cps") + self._parent.sendcmd("cps") - class Modulation(object): + class _Modulation(object): """ Options and functions related to the laser's optical output modulation. """ def __init__(self, parent): - self.parent = parent + self._parent = parent + self._enabled = None @property def on_time(self): """ - Get the on time of TTL modulation, in mS + Get/Set the on time of TTL modulation, in mS :return: the on time in the TTL modulation. - :rtype: quantities.mS + :rtype: ~quantities.Quantity """ - response = self.parent.query("stsont?") + response = self._parent.query("stsont?") return float(response)*pq.mS @on_time.setter def on_time(self, newval): - """ - Set the on time og TTL modulation, in mS. - :param newval: the modulation on time, in mS. - :type newval: quantities.mS - """ - newval = newval.rescale('mS').magnitude - self.parent.sendcmd("stsont:"+str(newval)) + newval = assume_units(newval, pq.ms).rescale('mS').magnitude + self._parent.sendcmd("stsont:"+str(newval)) @property def off_time(self): """ - Get the off time of TTL modulation, in mS + Get/Set the off time of TTL modulation, in mS :return: the off time in the TTL modulation. - :rtype: quantities.mS + :rtype: ~quantities.Quantity """ - response = self.parent.query("stsofft?") + response = self._parent.query("stsofft?") return float(response)*pq.mS @off_time.setter def off_time(self, newval): - """ - Set the off time og TTL modulation, in mS. - :param newval: the modulation off time, in mS. - :type newval: quantities.mS - """ - newval = newval.rescale('mS').magnitude - self.parent.sendcmd("stsofft:"+str(newval)) + newval = assume_units(newval, pq.ms).rescale('mS').magnitude + self._parent.sendcmd("stsofft:"+str(newval)) - def start(self): + @property + def enabled(self): """ - Start TTL modulation. + Get/Set the TTL modulation output state. + :return: the TTL modulation state + :rtype: bool """ - self.parent.sendcmd("stm") + return self._enabled - def stop(self): - """ - Stop TTl modulation. - """ - self.parent.sendcmd("ctm") + @enabled.setter + def enabled(self, newval): + if newval: + self._parent.sendcmd("stm") + else: + self._parent.sendcmd("ctm") + self._enabled = newval - class ThermoElectricCooler(object): + class _ThermoElectricCooler(object): """ Options and functions relating to the laser diode's thermo electric cooler. """ def __init__(self, parent): - self.parent = parent + self._parent = parent + self._enabled = None @property def current(self): """ Get the current TEC current :return: the TEC current - :rtype: quantities.mA + :rtype: ~quantities.Quantity """ - response = self.parent.query("rti?") + response = self._parent.query("rti?") return float(response)*pq.mA @property def target(self): """ - Return the TEC target temperature + Get the TEC target temperature :return: The target temperature - :rtype: quantities.degC + :rtype: ~quantities.Quantity """ - response = self.parent.query("rstt?") + response = self._parent.query("rstt?") return float(response)*pq.degC - def enable(self): + @property + def enabled(self): """ - Enable TEC control + Get/Set the enable state fo the TEC. + :return: The TEC's enabled state. + :rtype: bool """ - self.parent.sendcmd("tecon") + return self._enabled - def shutdown(self): - """ - Shut down TEC control - """ - self.parent.sendcmd("tecoff") + @enabled.setter + def enabled(self, newval): + if newval: + self._parent.sendcmd("tecon") + else: + self._parent.sendcmd("tecoff") + self._enabled = newval def _ack_expected(self, msg=""): if msg.find("?") > 0: @@ -258,63 +272,48 @@ def firmware(self): @property def current(self): """ - The laser diode current, in mA. + Get/Set the laser diode current, in mA. :return: The laser diode current. - :rtype: pq.mA + :rtype: ~quantities.Quantity """ response = self.query("rli?") return float(response)*pq.mA @current.setter def current(self, newval): - """ - Set the laser diode current. - :param newval: the - """ - newval = newval.rescale('mA').magnitude + newval = assume_units(newval, pq.mA).rescale('mA').magnitude self.sendcmd("slc:"+str(newval)) @property def maximum_current(self): """ - Read the maximum laser diode current in mA. + Get/Set the maximum laser diode current in mA. If the current is set + over the limit, the laser will shut down. + :return: The maximum laser diode current. - :rtype: pq.mA + :rtype: ~quantities.Quantity """ response = self.query("rlcm?") return float(response)*pq.mA @maximum_current.setter def maximum_current(self, newval): - """ - Set the maximum laser diode current in - mA, if the current is over the limit the laser - will shut down. - - :param newval: the new current. - :type newval: quantities.mA - """ - newval = newval.rescale("mA").magnitude + newval = assume_units(newval, pq.mA).rescale('mA').magnitude self.sendcmd("smlc:" + str(newval)) @property def power(self): """ - The laser's optical power. + Get/Set the laser's optical power. :return: the laser power, in mW - :rtype: quantities.mW + :rtype: ~quantities.Quantity """ response = self.query("rlp?") return float(response)*pq.mW @power.setter def power(self, newval): - """ - Set the optical power. - :param newval: the new power, in mW. - :type newval: quantities.mW - """ - newval = newval.rescale('mW').magnitude + newval = assume_units(newval, pq.mW).rescale('mW').magnitude self.sendcmd("slp:"+str(newval)) @property @@ -340,34 +339,34 @@ def status(self): @property def temperature(self): """ - Get the current laser diode temperature. + Get/Set the current laser diode temperature. :return: The current laser diode temperature. - :rtype: quantity.degC + :rtype: ~quantities.Quantity """ response = self.query("rtt?") return float(response)*pq.degC @temperature.setter def temperature(self, newval): - """ - Set the laser diode temperature - :param newval: the new temperature. - :type newval: quantities.degC - """ newval = convert_temperature(newval, pq.degC).magnitude self.sendcmd("stt:"+str(newval)) - def enable(self): + @property + def enabled(self): """ - Enable laser emission. + Enable/disable laser emission. + :return: True for enabled, False for disabled. + :rtype: bool """ - self.sendcmd("lon") + return self._enabled - def disable(self): - """ - Disable laser emission. - """ - self.sendcmd("loff") + @enabled.setter + def enabled(self, newval): + if newval: + self.sendcmd("lon") + else: + self.sendcmd("loff") + self._enabled = newval def save(self): """ diff --git a/instruments/tests/test_ondax/test_lm.py b/instruments/tests/test_ondax/test_lm.py index 2df050540..c141aef97 100644 --- a/instruments/tests/test_ondax/test_lm.py +++ b/instruments/tests/test_ondax/test_lm.py @@ -29,7 +29,8 @@ def test_acc_enable(): ], sep="\r" ) as lm: - lm.acc.enable() + lm.acc.enabled = True + assert lm.acc.enabled def test_acc_disable(): @@ -43,7 +44,8 @@ def test_acc_disable(): ], sep="\r" ) as lm: - lm.acc.disable() + lm.acc.enabled = False + assert not lm.acc.enabled def test_acc_on(): @@ -99,7 +101,8 @@ def test_apc_enable(): ], sep="\r" ) as lm: - lm.apc.enable() + lm.apc.enabled = True + assert lm.apc.enabled def test_apc_disable(): @@ -113,7 +116,8 @@ def test_apc_disable(): ], sep="\r" ) as lm: - lm.apc.disable() + lm.apc.enabled = False + assert not lm.apc.enabled def test_apc_start(): @@ -178,7 +182,7 @@ def test_modulation_off_time(): lm.modulation.off_time = 20 * quantities.mS -def test_modulation_start(): +def test_modulation_enabled(): with expected_protocol( ondax.LM, [ @@ -189,7 +193,8 @@ def test_modulation_start(): ], sep="\r" ) as lm: - lm.modulation.start() + lm.modulation.enabled = True + assert lm.modulation.enabled def test_modulation_stop(): @@ -203,7 +208,8 @@ def test_modulation_stop(): ], sep="\r" ) as lm: - lm.modulation.stop() + lm.modulation.enabled = False + assert not lm.modulation.enabled def test_tec_current(): @@ -245,10 +251,11 @@ def test_tec_enable(): ], sep="\r" ) as lm: - lm.tec.enable() + lm.tec.enabled = True + assert lm.tec.enabled -def test_tec_shutdown(): +def test_tec_disable(): with expected_protocol( ondax.LM, [ @@ -259,7 +266,8 @@ def test_tec_shutdown(): ], sep="\r" ) as lm: - lm.tec.shutdown() + lm.tec.enabled = False + assert not lm.tec.enabled def test_firmware(): @@ -383,7 +391,8 @@ def test_enable(): ], sep="\r" ) as lm: - lm.enable() + lm.enabled = True + assert lm.enabled def test_disable(): @@ -397,7 +406,8 @@ def test_disable(): ], sep="\r" ) as lm: - lm.disable() + lm.enabled = False + assert not lm.enabled def test_save(): From 29a6213e736ceee45649a78c084a210276262d77 Mon Sep 17 00:00:00 2001 From: Steven Casagrande Date: Wed, 31 Aug 2016 00:15:03 -0400 Subject: [PATCH 3/4] Ondax LM: Fix wrong units, improve docstrings --- doc/source/apiref/ondax.rst | 16 ++ instruments/ondax/lm.py | 301 ++++++++++++++++++------ instruments/tests/test_ondax/test_lm.py | 8 +- 3 files changed, 253 insertions(+), 72 deletions(-) create mode 100644 doc/source/apiref/ondax.rst 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/lm.py b/instruments/ondax/lm.py index da20dadfb..11070253a 100644 --- a/instruments/ondax/lm.py +++ b/instruments/ondax/lm.py @@ -18,7 +18,6 @@ from instruments.abstract_instruments import Instrument from instruments.util_fns import convert_temperature, assume_units - # CLASSES ##################################################################### @@ -26,8 +25,9 @@ class LM(Instrument): """ The LM is the Ondax SureLock VHG-stabilized laser diode. - The user manual can be found here: - http://www.ondax.com/Downloads/SureLock/Compact%20laser%20module%20manual.pdf + 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): @@ -42,7 +42,7 @@ def __init__(self, filelike): # ENUMS # class Status(IntEnum): """ - Enum containing the states of the laser + Enum containing the valid states of the laser """ normal = 1 inner_modulation = 2 @@ -58,18 +58,31 @@ class Status(IntEnum): class _AutomaticCurrentControl(object): """ Options and functions related to the laser diode's automatic current - control driver + 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 = None + self._enabled = False @property def target(self): """ - The ACC target current + 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 - :rtype: ~quantities.Quantity + :units: mA + :type: `~quantities.Quantity` """ response = float(self._parent.query("rstli?")) return response*pq.mA @@ -77,15 +90,26 @@ def target(self): @property def enabled(self): """ - Get/Set the enabled state of the ACC driver + Get/Set the enabled state of the ACC driver. + + This property is accessed via the `LM.acc` namespace. - :return: The enabled state of the ACC driver - :rtype: bool + 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: @@ -94,13 +118,29 @@ def enabled(self, newval): def on(self): """ - Turn on the ACC driver + 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 ACC driver + 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") @@ -108,17 +148,30 @@ 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 = None + self._enabled = False @property def target(self): """ - Return the target laser power in mW + 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 - :rtype: ~quantities.Quantities + :units: mW + :type: `~quantities.Quantities` """ response = self._parent.query("rslp?") return float(response)*pq.mW @@ -126,14 +179,26 @@ def target(self): @property def enabled(self): """ - Get/Set the enabled state of the APC driver - :return: The enabled state of the APC driver - :rtype: bool + 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: @@ -142,65 +207,122 @@ def enabled(self, newval): def start(self): """ - Start the APC scan. + 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 APC scan. + 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 = None + self._enabled = False @property def on_time(self): """ - Get/Set the on time of TTL modulation, in mS - :return: the on time in the TTL modulation. - :rtype: ~quantities.Quantity + 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 + return float(response)*pq.ms @on_time.setter def on_time(self, newval): - newval = assume_units(newval, pq.ms).rescale('mS').magnitude + newval = assume_units(newval, pq.ms).rescale(pq.ms).magnitude self._parent.sendcmd("stsont:"+str(newval)) @property def off_time(self): """ - Get/Set the off time of TTL modulation, in mS - :return: the off time in the TTL modulation. - :rtype: ~quantities.Quantity + 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 + return float(response)*pq.ms @off_time.setter def off_time(self, newval): - newval = assume_units(newval, pq.ms).rescale('mS').magnitude + 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. - :return: the TTL modulation state - :rtype: bool + + 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: @@ -211,17 +333,29 @@ 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 = None + self._enabled = False @property def current(self): """ - Get the current TEC current - :return: the TEC current - :rtype: ~quantities.Quantity + 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 @@ -229,9 +363,18 @@ def current(self): @property def target(self): """ - Get the TEC target temperature - :return: The target temperature - :rtype: ~quantities.Quantity + 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 @@ -239,14 +382,26 @@ def target(self): @property def enabled(self): """ - Get/Set the enable state fo the TEC. - :return: The TEC's enabled state. - :rtype: bool + 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: @@ -262,9 +417,9 @@ def _ack_expected(self, msg=""): @property def firmware(self): """ - Return the laser system firmware version. - :return: The laser system firmware. - :rtype: str + Gets the laser system firmware version. + + :type: `str` """ response = self.query("rsv?") return response @@ -272,16 +427,18 @@ def firmware(self): @property def current(self): """ - Get/Set the laser diode current, in mA. - :return: The laser diode current. - :rtype: ~quantities.Quantity + 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('mA').magnitude + newval = assume_units(newval, pq.mA).rescale(pq.mA).magnitude self.sendcmd("slc:"+str(newval)) @property @@ -290,8 +447,9 @@ 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. - :return: The maximum laser diode current. - :rtype: ~quantities.Quantity + :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 @@ -304,24 +462,26 @@ def maximum_current(self, newval): @property def power(self): """ - Get/Set the laser's optical power. - :return: the laser power, in mW - :rtype: ~quantities.Quantity + 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('mW').magnitude + newval = assume_units(newval, pq.mW).rescale(pq.mW).magnitude self.sendcmd("slp:"+str(newval)) @property def serial_number(self): """ - Return the laser controller serial number - :return: the serial number, as a string. - :rtype: str + Gets the laser controller serial number + + :type: `str` """ response = self.query("rsn?") return response @@ -330,8 +490,8 @@ def serial_number(self): def status(self): """ Read laser controller run status. - :return: The status. - :rtype: LM.Status + + :type: `LM.Status` """ response = self.query("rlrs?") return self.Status(int(response)) @@ -339,9 +499,11 @@ def status(self): @property def temperature(self): """ - Get/Set the current laser diode temperature. - :return: The current laser diode temperature. - :rtype: ~quantities.Quantity + 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 @@ -354,14 +516,17 @@ def temperature(self, newval): @property def enabled(self): """ - Enable/disable laser emission. - :return: True for enabled, False for disabled. - :rtype: bool + 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: diff --git a/instruments/tests/test_ondax/test_lm.py b/instruments/tests/test_ondax/test_lm.py index c141aef97..b0f544b74 100644 --- a/instruments/tests/test_ondax/test_lm.py +++ b/instruments/tests/test_ondax/test_lm.py @@ -161,8 +161,8 @@ def test_modulation_on_time(): ], sep="\r" ) as lm: - assert lm.modulation.on_time == 10 * quantities.mS - lm.modulation.on_time = 20 * quantities.mS + assert lm.modulation.on_time == 10 * quantities.ms + lm.modulation.on_time = 20 * quantities.ms def test_modulation_off_time(): @@ -178,8 +178,8 @@ def test_modulation_off_time(): ], sep="\r" ) as lm: - assert lm.modulation.off_time == 10 * quantities.mS - lm.modulation.off_time = 20 * quantities.mS + assert lm.modulation.off_time == 10 * quantities.ms + lm.modulation.off_time = 20 * quantities.ms def test_modulation_enabled(): From 074d72f6080bfaf2dd8c71414eb4a42bf909ef4f Mon Sep 17 00:00:00 2001 From: Steven Casagrande Date: Wed, 31 Aug 2016 09:18:31 -0400 Subject: [PATCH 4/4] Ondax LM: Add additional unit tests to cover bool type guards --- instruments/tests/test_ondax/test_lm.py | 72 ++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/instruments/tests/test_ondax/test_lm.py b/instruments/tests/test_ondax/test_lm.py index b0f544b74..93c89de29 100644 --- a/instruments/tests/test_ondax/test_lm.py +++ b/instruments/tests/test_ondax/test_lm.py @@ -1,8 +1,22 @@ +#!/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( @@ -48,6 +62,17 @@ def test_acc_disable(): 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, @@ -120,6 +145,18 @@ def test_apc_disable(): 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, @@ -197,7 +234,7 @@ def test_modulation_enabled(): assert lm.modulation.enabled -def test_modulation_stop(): +def test_modulation_disabled(): with expected_protocol( ondax.LM, [ @@ -212,6 +249,17 @@ def test_modulation_stop(): 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, @@ -270,6 +318,17 @@ def test_tec_disable(): 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, @@ -410,6 +469,17 @@ def test_disable(): 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,