diff --git a/instruments/doc/examples/ex_thorlabstc200.py b/instruments/doc/examples/ex_thorlabstc200.py new file mode 100644 index 000000000..10e9db72b --- /dev/null +++ b/instruments/doc/examples/ex_thorlabstc200.py @@ -0,0 +1,41 @@ +#Thorlabs Temperature Controller example + +import instruments as ik +import quantities +tc = ik.thorlabs.TC200.open_serial('/dev/tc200', 115200) + + +tc.temperature = 70*quantities.degF +print("The current temperature is: ", tc.temperature) + +tc.mode = tc.Mode.normal +print("The current mode is: ", tc.mode) + +tc.enable = True +print("The current enabled state is: ", tc.enable) + +tc.p = 200 +print("The current p gain is: ", tc.p) + +tc.i = 2 +print("The current i gain is: ", tc.i) + +tc.d = 2 +print("The current d gain is: ", tc.d) + +tc.degrees = quantities.degF +print("The current degrees settings is: ", tc.degrees) + +tc.sensor = tc.Sensor.ptc100 +print("The current sensor setting is: ", tc.sensor) + +tc.beta = 3900 +print("The current beta settings is: ", tc.beta) + +tc.max_temperature = 150*quantities.degC +print("The current max temperature setting is: ", tc.max_temperature) + +tc.max_power = 1000*quantities.mW +print("The current max power setting is: ", tc.max_power) + + diff --git a/instruments/doc/examples/ex_topticatopmode.py b/instruments/doc/examples/ex_topticatopmode.py new file mode 100644 index 000000000..9c3fe0d29 --- /dev/null +++ b/instruments/doc/examples/ex_topticatopmode.py @@ -0,0 +1,41 @@ +#Thorlabs Temperature Controller example + +import instruments as ik +import quantities +tm = ik.toptica.TopMode.open_serial('/dev/ttyACM0', 115200) + + +print("The current emission state is: ", tm.enable) +print("The current lock state is: ", tm.locked) +print("The current interlock state is: ", tm.interlock) +print("The current fpga state is: ", tm.fpga_status) +print("The current temperature state is: ", tm.temperature_status) +print("The current current state is: ", tm.current_status) + +print("The laser1's serial number is: ", tm.laser1.serial_number) +print("The laser1's model number is: ", tm.laser1.model) +print("The laser1's wavelength is: ", tm.laser1.wavelength) +print("The laser1's production date is: ", tm.laser1.production_date) +print("The laser1's enable state is: ", tm.laser1.enable) +print("The laser1's up time is: ", tm.laser1.on_time) +print("The laser1's charm state is: ", tm.laser1.charm_status) +print("The laser1's temperature controller state is: ", tm.laser1.temperature_control_status) +print("The laser1's current controller state is: ", tm.laser1.current_control_status) +print("The laser1's tec state is: ", tm.laser1.tec_status) +print("The laser1's intensity is: ", tm.laser1.intensity) +print("The laser1's mode hop state is: ", tm.laser1.mode_hop) +print("The laser1's lock start time is: ", tm.laser1.lock_start) +print("The laser1's first mode hop time is: ", tm.laser1.first_mode_hop_time) +print("The laser1's latest mode hop time is: ", tm.laser1.latest_mode_hop_time) +print("The laser1's correction status is: ", tm.laser1.correction_status) + + + + + + + + + + + diff --git a/instruments/instruments/__init__.py b/instruments/instruments/__init__.py index 2744508f6..d54d1b344 100644 --- a/instruments/instruments/__init__.py +++ b/instruments/instruments/__init__.py @@ -63,6 +63,7 @@ def __check_versions(): import instruments.rigol import instruments.srs import instruments.tektronix +import instruments.toptica import instruments.thorlabs import instruments.qubitekk import instruments.hp diff --git a/instruments/instruments/abstract_instruments/comm/serial_communicator.py b/instruments/instruments/abstract_instruments/comm/serial_communicator.py index 9fcb3abfd..b61ff86b4 100644 --- a/instruments/instruments/abstract_instruments/comm/serial_communicator.py +++ b/instruments/instruments/abstract_instruments/comm/serial_communicator.py @@ -104,10 +104,10 @@ def close(self): self._conn.close() def read(self, size): - if (size >= 0): + if size >= 0: resp = self._conn.read(size) return resp - elif (size == -1): + elif size == -1: result = bytearray() c = 0 while c != self._terminator: diff --git a/instruments/instruments/abstract_instruments/instrument.py b/instruments/instruments/abstract_instruments/instrument.py index 7ef5a612d..fd7ac97aa 100644 --- a/instruments/instruments/abstract_instruments/instrument.py +++ b/instruments/instruments/abstract_instruments/instrument.py @@ -83,7 +83,15 @@ class Instrument(object): # This can and should be overriden in subclasses for instruments # that use different terminators. _terminator = "\n" - + + # some instruments issue prompt characters to indicate that it is ready for input. This must be eliminated from + # the response + _prompt = "" + + # Certain instruments (such as those produced by Thorlabs) echo the command back before producing the response. + # this boolean handles that change. + _echo = False + def __init__(self, filelike): # Check to make sure filelike is a subclass of AbstractCommunicator if isinstance(filelike, AbstractCommunicator): @@ -115,7 +123,12 @@ def query(self, cmd, size=-1): connected instrument. :rtype: `str` """ - return self._file.query(cmd, size) + if not self._echo: + return self._file.query(cmd, size).replace(self.prompt, "") + else: + response = self._file.query(cmd, size) + response = self.readline().replace(self.prompt, "").replace(cmd, "").replace(self.terminator, "") + return response def read(self, size=-1): """ @@ -129,6 +142,13 @@ def read(self, size=-1): """ return self._file.read(size) + def readline(self): + """ + Read a full line + :return: the read line + :rtype: `str` + """ + return self._file.readline() ## PROPERTIES ## @@ -142,6 +162,7 @@ def timeout(self): :type: `int` ''' return self._file.timeout + @timeout.setter def timeout(self, newval): self._file.timeout = newval @@ -159,6 +180,7 @@ def address(self): :type: `int` for GPIB address, `str` for other ''' return self._file.address + @address.setter def address(self, newval): self._file.address = newval @@ -175,10 +197,42 @@ def terminator(self): :type: `int`, or `str` for GPIB adapters. ''' return self._file.terminator + @terminator.setter def terminator(self, newval): self._file.terminator = newval - + + @property + def prompt(self): + ''' + Gets/sets the prompt used for communication. + + For communication options where this is applicable, the value + corresponds to the character used for prompt + + :type: `int`, or `str` for GPIB adapters. + ''' + if not hasattr(self._file, 'prompt'): + self._file.prompt = self._prompt + return self._file.prompt + + @prompt.setter + def prompt(self, newval): + self._file.prompt = newval + + @property + def echo(self): + ''' + Gets/sets the echo setting + :type: `bool` + ''' + + return self._echo + + @echo.setter + def echo(self, newval): + self._echo = newval + ## BASIC I/O METHODS ## def write(self, msg): diff --git a/instruments/instruments/tests/test_thorlabs/__init__.py b/instruments/instruments/tests/test_thorlabs/__init__.py index a945ba1cd..fb97c7da4 100644 --- a/instruments/instruments/tests/test_thorlabs/__init__.py +++ b/instruments/instruments/tests/test_thorlabs/__init__.py @@ -26,107 +26,255 @@ import instruments as ik from instruments.tests import expected_protocol, make_name_test, unit_eq - -import cStringIO as StringIO +from nose.tools import raises +from flufl.enum import IntEnum import quantities as pq ## TESTS ###################################################################### + +@raises(ValueError) +def test_voltage_latch_min(): + ik.thorlabs.voltage_latch(-2) + + +@raises(ValueError) +def test_voltage_latch_max(): + ik.thorlabs.voltage_latch(9999) + + +@raises(ValueError) +def test_slot_latch_min(): + ik.thorlabs.slot_latch(-2) + + +@raises(ValueError) +def test_slot_latch_max(): + ik.thorlabs.slot_latch(9999) + + +def test_lcc25_name(): + with expected_protocol( + ik.thorlabs.LCC25, + "*idn?\r", + "*idn?\r>bloopbloop\r>\r" + ) as lcc: + assert lcc.name() == "bloopbloop" + + def test_lcc25_frequency(): with expected_protocol( ik.thorlabs.LCC25, "freq?\rfreq=10.0\r", - "\r>20\r" + "freq?\r>20\r" ) as lcc: unit_eq(lcc.frequency, pq.Quantity(20, "Hz")) lcc.frequency = 10.0 + +@raises(ValueError) +def test_lcc25_frequency_lowlimit(): + with expected_protocol( + ik.thorlabs.LCC25, + "freq=0.0\r", + "freq=0.0\r>\r" + ) as lcc: + lcc.frequency = 0.0 + + +@raises(ValueError) +def test_lcc25_frequency_highlimit(): + with expected_protocol( + ik.thorlabs.LCC25, + "freq=160.0\r", + "freq=160.0\r>\r" + ) as lcc: + lcc.frequency = 160.0 + + def test_lcc25_mode(): with expected_protocol( ik.thorlabs.LCC25, "mode?\rmode=1\r", - "\r>2\r" + "mode?\r>2\r" ) as lcc: assert lcc.mode == ik.thorlabs.LCC25.Mode.voltage2 lcc.mode = ik.thorlabs.LCC25.Mode.voltage1 + +@raises(TypeError) +def test_lcc25_mode_invalid(): + with expected_protocol( + ik.thorlabs.LCC25, + "mode=10\r", + "mode=10\r>0\r>\r" + ) as lcc: + lcc.mode = "blo" + + +@raises(TypeError) +def test_lcc25_mode_invalid2(): + with expected_protocol( + ik.thorlabs.LCC25, + "mode=10\r", + "mode=10\r>0\r>\r" + ) as lcc: + blo = IntEnum("blo", "beep boop bop") + lcc.mode = blo.beep + + def test_lcc25_enable(): with expected_protocol( ik.thorlabs.LCC25, "enable?\renable=1\r", - ">\r>0\r" + "enable?>\r>0\r" ) as lcc: assert lcc.enable == False lcc.enable = True + +@raises(TypeError) +def test_lcc25_enable_type(): + with expected_protocol( + ik.thorlabs.LCC25, + "blo\r", + "blo\rblo\r>\r" + ) as lcc: + lcc.enable = "blo" + + def test_lcc25_extern(): with expected_protocol( ik.thorlabs.LCC25, "extern?\rextern=1\r", - ">\r>0\r" + "extern?>\r>0\r" ) as lcc: assert lcc.extern == False lcc.extern = True + +@raises(TypeError) +def test_tc200_extern_type(): + with expected_protocol( + ik.thorlabs.LCC25, + "blo\r", + "blo\rblo\r>\r" + ) as tc: + tc.extern = "blo" + + def test_lcc25_remote(): with expected_protocol( ik.thorlabs.LCC25, "remote?\rremote=1\r", - ">\r>0\r" + "remote?>\r>0\r" ) as lcc: assert lcc.remote == False lcc.remote = True + +@raises(TypeError) +def test_tc200_remote_type(): + with expected_protocol( + ik.thorlabs.LCC25, + "blo\r", + "blo\rblo\r>\r" + ) as tc: + tc.remote = "blo" + + def test_lcc25_voltage1(): with expected_protocol( ik.thorlabs.LCC25, "volt1?\rvolt1=10.0\r", - "\r20\r" + "volt1?\r>20\r" ) as lcc: unit_eq(lcc.voltage1, pq.Quantity(20, "V")) lcc.voltage1 = 10.0 + +def test_check_cmd(): + assert ik.thorlabs.check_cmd("blo") == 1 + assert ik.thorlabs.check_cmd("CMD_NOT_DEFINED") == 0 + assert ik.thorlabs.check_cmd("CMD_ARG_INVALID") == 0 + + def test_lcc25_voltage2(): with expected_protocol( ik.thorlabs.LCC25, "volt2?\rvolt2=10.0\r", - "\r20\r" + "volt2?\r>20\r" ) as lcc: unit_eq(lcc.voltage2, pq.Quantity(20, "V")) lcc.voltage2 = 10.0 + def test_lcc25_minvoltage(): with expected_protocol( ik.thorlabs.LCC25, "min?\rmin=10.0\r", - "\r>20\r" + "min?\r>20\r" ) as lcc: unit_eq(lcc.min_voltage, pq.Quantity(20, "V")) lcc.min_voltage = 10.0 + +def test_lcc25_maxvoltage(): + with expected_protocol( + ik.thorlabs.LCC25, + "max?\rmax=10.0\r", + "max?\r>20\r" + ) as lcc: + unit_eq(lcc.max_voltage, pq.Quantity(20, "V")) + lcc.max_voltage = 10.0 + + def test_lcc25_dwell(): with expected_protocol( ik.thorlabs.LCC25, "dwell?\rdwell=10\r", - "\r>20\r" + "dwell?\r>20\r" ) as lcc: unit_eq(lcc.dwell, pq.Quantity(20, "ms")) lcc.dwell = 10 + +@raises(ValueError) +def test_lcc25_dwell_positive(): + with expected_protocol( + ik.thorlabs.LCC25, + "dwell=-10\r", + "dwell=-10\r>\r" + ) as lcc: + lcc.dwell = -10 + + def test_lcc25_increment(): with expected_protocol( ik.thorlabs.LCC25, "increment?\rincrement=10.0\r", - "\r>20\r" + "increment?\r>20\r" ) as lcc: unit_eq(lcc.increment, pq.Quantity(20, "V")) lcc.increment = 10.0 + +@raises(ValueError) +def test_lcc25_increment_positive(): + with expected_protocol( + ik.thorlabs.LCC25, + "increment=-10\r", + "increment=-10\r>\r" + ) as lcc: + lcc.increment = -10 + + + def test_lcc25_default(): with expected_protocol( ik.thorlabs.LCC25, "default\r", - "\r>\r" + "default\r>\r" ) as lcc: lcc.default() @@ -134,151 +282,552 @@ def test_lcc25_save(): with expected_protocol( ik.thorlabs.LCC25, "save\r", - "\r>\r" + "save\r>\r" ) as lcc: lcc.save() + def test_lcc25_save_settings(): with expected_protocol( ik.thorlabs.LCC25, "set=2\r", - "\r>\r" + "set=2\r>\r" ) as lcc: lcc.set_settings(2) + def test_lcc25_get_settings(): with expected_protocol( ik.thorlabs.LCC25, "get=2\r", - "\r>\r" + "get=2\r>\r" ) as lcc: lcc.get_settings(2) + def test_lcc25_test_mode(): with expected_protocol( ik.thorlabs.LCC25, "test\r", - "\r>\r" + "test\r>\r" ) as lcc: lcc.test_mode() + +def test_sc10_name(): + with expected_protocol( + ik.thorlabs.SC10, + "id?\r", + "id?\r>bloopbloop\r>\r" + ) as sc: + assert sc.name() == "bloopbloop" + + def test_sc10_enable(): with expected_protocol( ik.thorlabs.SC10, "ens?\rens=1\r", - "\r>0\r>\r" + "ens?\r>0\r>\r" ) as sc: assert sc.enable == 0 sc.enable = 1 + +@raises(ValueError) +def test_sc10_enable_invalid(): + with expected_protocol( + ik.thorlabs.SC10, + "ens=10\r", + "ens=10\r>0\r>\r" + ) as sc: + sc.enable = 10 + + def test_sc10_repeat(): with expected_protocol( ik.thorlabs.SC10, "rep?\rrep=10\r", - "\r>20\r>\r" + "rep?\r>20\r>\r" ) as sc: assert sc.repeat == 20 sc.repeat = 10 + +@raises(ValueError) +def test_sc10_repeat_invalid(): + with expected_protocol( + ik.thorlabs.SC10, + "rep=-1\r", + "rep=-1\r>0\r>\r" + ) as sc: + sc.repeat = -1 + + def test_sc10_mode(): with expected_protocol( ik.thorlabs.SC10, "mode?\rmode=2\r", - "\r>1\r>\r" + "mode?\r>1\r>\r" ) as sc: assert sc.mode == ik.thorlabs.SC10.Mode.manual sc.mode = ik.thorlabs.SC10.Mode.auto + +@raises(TypeError) +def test_sc10_mode_invalid(): + with expected_protocol( + ik.thorlabs.SC10, + "mode=10\r", + "mode=10\r>0\r>\r" + ) as sc: + sc.mode = "blo" + + +@raises(TypeError) +def test_sc10_mode_invalid2(): + with expected_protocol( + ik.thorlabs.SC10, + "mode=10\r", + "mode=10\r>0\r>\r" + ) as sc: + blo = IntEnum("blo", "beep boop bop") + sc.mode = blo.beep + + def test_sc10_trigger(): with expected_protocol( ik.thorlabs.SC10, "trig?\rtrig=1\r", - "\r>0\r>\r" + "trig?\r>0\r>\r" ) as sc: assert sc.trigger == 0 sc.trigger = 1 + +@raises(ValueError) +def test_trigger_check(): + ik.thorlabs.trigger_check(2) + + +@raises(ValueError) +def test_time_check_min(): + ik.thorlabs.check_time(-1) + + +@raises(ValueError) +def test_time_check_max(): + ik.thorlabs.check_time(9999999) + + def test_sc10_out_trigger(): with expected_protocol( ik.thorlabs.SC10, "xto?\rxto=1\r", - "\r>0\r>\r" + "xto?\r>0\r>\r" ) as sc: assert sc.out_trigger == 0 sc.out_trigger = 1 + def test_sc10_open_time(): with expected_protocol( ik.thorlabs.SC10, "open?\ropen=10\r", - "\r20\r>\r" + "open?\r>20\r>\r" ) as sc: unit_eq(sc.open_time, pq.Quantity(20, "ms")) sc.open_time = 10.0 + def test_sc10_shut_time(): with expected_protocol( ik.thorlabs.SC10, "shut?\rshut=10\r", - "\r20\r>\r" + "shut?\r>20\r>\r" ) as sc: unit_eq(sc.shut_time, pq.Quantity(20, "ms")) sc.shut_time = 10.0 -''' -unit test for baud rate should be done very carefully, testing to change the -baud rate to something other then the current baud rate will cause the -connection to be unreadable. + def test_sc10_baud_rate(): with expected_protocol(ik.thorlabs.SC10, "baud?\rbaud=1\r", "\r>0\r>\r") as sc: - assert sc.baud_rate ==0 - sc.baud_rate = 1 -''' + assert sc.baud_rate == 9600 + sc.baud_rate = 115200 + + +def test_echo(): + with expected_protocol(ik.thorlabs.SC10, "baud?\r", "\r>0\r>\r") as sc: + assert sc.baud_rate == 9600 + assert sc.echo == True + + +@raises(ValueError) +def test_sc10_baud_rate_error(): + with expected_protocol(ik.thorlabs.SC10, "\rbaud=1\r", "\r>\r") as sc: + sc.baud_rate = 115201 + def test_sc10_closed(): with expected_protocol( ik.thorlabs.SC10, "closed?\r", - "\r1\r" + "closed?\r>1\r>" ) as sc: assert sc.closed + def test_sc10_interlock(): with expected_protocol( ik.thorlabs.SC10, "interlock?\r", - "\r1\r" + "interlock?\r>1\r>" ) as sc: assert sc.interlock + def test_sc10_default(): with expected_protocol( ik.thorlabs.SC10, "default\r", - "\r1\r" + "default\r>1\r>" ) as sc: assert sc.default() + def test_sc10_save(): with expected_protocol( ik.thorlabs.SC10, "savp\r", - "\r1\r" + "savp\r>1\r>" ) as sc: assert sc.save() + def test_sc10_save_mode(): with expected_protocol( ik.thorlabs.SC10, "save\r", - "\r1\r" + "save\r>1\r>" ) as sc: assert sc.save_mode() + def test_sc10_restore(): with expected_protocol( ik.thorlabs.SC10, "resp\r", - "\r1\r" + "resp\r>1\r>" ) as sc: - assert sc.restore() \ No newline at end of file + assert sc.restore() + + +def test_tc200_name(): + with expected_protocol( + ik.thorlabs.TC200, + "*idn?\r", + "*idn?\r>bloopbloop\r>\r" + ) as tc: + assert tc.name() == "bloopbloop" + + +def test_tc200_mode(): + with expected_protocol( + ik.thorlabs.TC200, + "stat?\rmode=cycle\r", + "stat?\r>54\r>\r" + ) as tc: + assert tc.mode == ik.thorlabs.TC200.Mode.normal + tc.mode = ik.thorlabs.TC200.Mode.cycle + + +def test_tc200_mode(): + with expected_protocol( + ik.thorlabs.TC200, + "stat?\rmode=cycle\r", + "stat?\r>54\r>\r" + ) as tc: + assert tc.mode == ik.thorlabs.TC200.Mode.normal + tc.mode = ik.thorlabs.TC200.Mode.cycle + + +@raises(TypeError) +def test_tc200_mode_error(): + with expected_protocol(ik.thorlabs.TC200, 'blo', 'blo') as tc: + tc.mode = "blo" + + +@raises(TypeError) +def test_tc200_mode_error2(): + with expected_protocol(ik.thorlabs.TC200, 'blo', 'blo') as tc: + blo = IntEnum("blo", "beep boop bop") + tc.mode = blo.beep + + +def test_tc200_enable(): + with expected_protocol( + ik.thorlabs.TC200, + ["stat?", "stat?", "ens", "stat?", "ens"], + ["stat?\r54\r>\n", "\r54\r>\n", "\r>\n", "\r55\r>\n", "\r>\n"], + sep="\r" + ) as tc: + assert tc.enable == 0 + tc.enable = True + tc.enable = False + + +@raises(TypeError) +def test_tc200_enable_type(): + with expected_protocol( + ik.thorlabs.TC200, + "blo\r", + "blo\rblo\r>\r" + ) as tc: + tc.enable = "blo" + + +def test_tc200_temperature(): + with expected_protocol( + ik.thorlabs.TC200, + ["tact?", "tmax?", "tset=40.0"], + ["tact?\r>30 C\r>\n", "\r>250\r>", "\r>\r"], + sep="\r" + ) as tc: + assert tc.temperature == 30.0*pq.degC + tc.temperature = 40*pq.degC + + +@raises(ValueError) +def test_tc200_temperature_range(): + with expected_protocol( + ik.thorlabs.TC200, + ["tmax?", "tset=50.0"], + ["tmax?\r>40\r>", "\r>\r"], + sep="\r" + ) as tc: + tc.temperature = 50*pq.degC + + +def test_tc200_pid(): + with expected_protocol( + ik.thorlabs.TC200, + "pid?\rpgain=2\r", + "pid?\r>2 0 220 \r>" + ) as tc: + assert tc.p == 2 + tc.p = 2 + + with expected_protocol( + ik.thorlabs.TC200, + "pid?\rigain=0\r", + "pid?\r>2 0 220 \r>\r" + ) as tc: + assert tc.i == 0 + tc.i = 0 + + with expected_protocol( + ik.thorlabs.TC200, + "pid?\rdgain=220\r", + "pid?\r>2 0 220 \r>\r" + ) as tc: + assert tc.d == 220 + tc.d = 220 + + +@raises(ValueError) +def test_tc200_pmin(): + with expected_protocol( + ik.thorlabs.TC200, + "pgain=-1\r", + "pgain=-1\r>\r" + ) as tc: + tc.p = -1 + + +@raises(ValueError) +def test_tc200_pmax(): + with expected_protocol( + ik.thorlabs.TC200, + "pgain=260\r", + "pgain=260\r>\r" + ) as tc: + tc.p = 260 + + +@raises(ValueError) +def test_tc200_imin(): + with expected_protocol( + ik.thorlabs.TC200, + "igain=-1\r", + "igain=-1\r>\r" + ) as tc: + tc.i = -1 + + +@raises(ValueError) +def test_tc200_imax(): + with expected_protocol( + ik.thorlabs.TC200, + "igain=260\r", + "igain=260\r>\r" + ) as tc: + tc.i = 260 + + +@raises(ValueError) +def test_tc200_dmin(): + with expected_protocol( + ik.thorlabs.TC200, + "dgain=-1\r", + "dgain=-1\r>\r" + ) as tc: + tc.d = -1 + + +@raises(ValueError) +def test_tc200_dmax(): + with expected_protocol( + ik.thorlabs.TC200, + "dgain=260\r", + "dgain=260\r>\r" + ) as tc: + tc.d = 260 + + +def test_tc200_degrees(): + with expected_protocol( + ik.thorlabs.TC200, + ["stat?", "stat?", "stat?", "unit=c", "unit=f", "unit=k"], + ["stat?\r>44\n", "\r>54\n", "\r>0\n", ">\r", ">\r", ">\r"], + sep="\r" + ) as tc: + assert str(tc.degrees).split(" ")[1] == "K" + assert str(tc.degrees).split(" ")[1] == "degC" + assert tc.degrees == pq.degF + + tc.degrees = pq.degC + tc.degrees = pq.degF + tc.degrees = pq.degK + + +@raises(TypeError) +def test_tc200_degrees_invalid(): + + with expected_protocol( + ik.thorlabs.TC200, + "unit=blo", + "unit=blo>\r" + ) as tc: + tc.degrees = "blo" + + +def test_tc200_sensor(): + with expected_protocol( + ik.thorlabs.TC200, + "sns?\rsns=ptc100\r", + "sns?\r>Sensor = NTC10K, Beta = 5600\r>\r" + ) as tc: + assert tc.sensor == tc.Sensor.ntc10k + tc.sensor = tc.Sensor.ptc100 + + +@raises(TypeError) +def test_tc200_sensor_error(): + with expected_protocol(ik.thorlabs.TC200, 'blo', 'blo') as tc: + tc.sensor = "blo" + + +@raises(TypeError) +def test_tc200_sensor_error2(): + with expected_protocol(ik.thorlabs.TC200, 'blo', 'blo') as tc: + blo = IntEnum("blo", "beep boop bop") + tc.sensor = blo.beep + + +def test_tc200_beta(): + with expected_protocol( + ik.thorlabs.TC200, + "beta?\rbeta=2000\r", + "beta?\r>5600\r>\r" + ) as tc: + assert tc.beta == 5600 + tc.beta = 2000 + + +@raises(ValueError) +def test_tc200_beta_min(): + with expected_protocol( + ik.thorlabs.TC200, + "beta=200\r", + "beta=200\r>\r" + ) as tc: + tc.beta = 200 + + +@raises(ValueError) +def test_tc200_beta_max(): + with expected_protocol( + ik.thorlabs.TC200, + "beta=20000\r", + "beta=20000\r>\r" + ) as tc: + tc.beta = 20000 + + +def test_tc200_max_power(): + with expected_protocol( + ik.thorlabs.TC200, + "pmax?\rPMAX=12.0\r", + "pmax?\r>15.0\r>\r" + ) as tc: + assert tc.max_power == 15.0*pq.W + tc.max_power = 12*pq.W + + +@raises(ValueError) +def test_tc200_power_min(): + with expected_protocol( + ik.thorlabs.TC200, + "PMAX=-2\r", + "PMAX=-2\r>\r" + ) as tc: + tc.max_power = -1 + + +@raises(ValueError) +def test_tc200_power_max(): + with expected_protocol( + ik.thorlabs.TC200, + "PMAX=20000\r", + "PMAX=20000\r>\r" + ) as tc: + tc.max_power = 20000 + + +def test_tc200_max_temperature(): + with expected_protocol( + ik.thorlabs.TC200, + "tmax?\rTMAX=180.0\r", + "tmax?\r>200.0\r>\r" + ) as tc: + assert tc.max_temperature == 200.0*pq.degC + tc.max_temperature = 180*pq.degC + + +@raises(ValueError) +def test_tc200_temp_min(): + with expected_protocol( + ik.thorlabs.TC200, + "TMAX=-2\r", + "TMAX=-2\r>\r" + ) as tc: + tc.max_temperature = -1 + + +@raises(ValueError) +def test_tc200_temp_max(): + with expected_protocol( + ik.thorlabs.TC200, + "TMAX=20000\r", + "TMAX=20000\r>\r" + ) as tc: + tc.max_temperature = 20000 diff --git a/instruments/instruments/tests/test_toptica/__init__.py b/instruments/instruments/tests/test_toptica/__init__.py new file mode 100644 index 000000000..7dc2d045f --- /dev/null +++ b/instruments/instruments/tests/test_toptica/__init__.py @@ -0,0 +1,325 @@ +import instruments as ik +from instruments.tests import expected_protocol, make_name_test, unit_eq +from nose.tools import raises +from flufl.enum import IntEnum +import quantities as pq +import datetime + + +def test_convert_boolean(): + assert ik.toptica.convert_toptica_boolean("bloof") == False + assert ik.toptica.convert_toptica_boolean("boot") == True + assert ik.toptica.convert_toptica_boolean("Error: -3") == None + +@raises(ValueError) +def test_convert_boolean_value(): + ik.toptica.convert_toptica_boolean("blo") + +def test_convert_toptica_datetime(): + blo = datetime.datetime.now() + blo_str = datetime.datetime.now().strftime("%b %d %Y %I:%M%p") + assert ik.toptica.convert_toptica_datetime('""\r') == None + blo2 = ik.toptica.convert_toptica_datetime(blo_str) + diff = blo - blo2 + assert diff.seconds < 60 + + +def test_serial_number(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'laser1:serial-number)", "(param-ref 'laser2:serial-number)"], + ["(param-ref 'laser1:serial-number)\nbloop1>", "(param-ref 'laser2:serial-number)\nbloop2>"], + sep="\n" + ) as tm: + assert tm.laser1.serial_number == "bloop1" + assert tm.laser2.serial_number == "bloop2" + + +def test_model(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'laser1:model)", "(param-ref 'laser2:model)"], + ["(param-ref 'laser1:model)\nbloop1>", "(param-ref 'laser2:model)\nbloop2>"], + sep="\n" + ) as tm: + assert tm.laser1.model == "bloop1" + assert tm.laser2.model == "bloop2" + + +def test_wavelength(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'laser1:wavelength)", "(param-ref 'laser2:wavelength)"], + ["(param-ref 'laser1:wavelength)\n640>", "(param-ref 'laser2:wavelength)\n405.3>"], + sep="\n" + ) as tm: + assert tm.laser1.wavelength == 640*pq.nm + assert tm.laser2.wavelength == 405.3*pq.nm + + +def test_laser_enable(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'laser1:emission)", "(param-set! 'laser1:enable-emission #t)"], + ["(param-ref 'laser1:emission)\n#f>", "(param-set! 'laser1:enable-emission #t)\n>"], + sep="\n" + ) as tm: + assert tm.laser1.enable == False + tm.laser1.enable = True + + +@raises(TypeError) +def test_laser_enable_error(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-set! 'laser1:enable-emission #t)"], + ["(param-set! 'laser1:enable-emission #t)\n>"], + sep="\n" + ) as tm: + tm.laser1.enable = 'True' + + +def test_laser_tec_status(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'laser1:tec:ready)"], + ["(param-ref 'laser1:tec:ready)\n#f>"], + sep="\n" + ) as tm: + assert tm.laser1.tec_status == False + + +def test_laser_intensity(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'laser1:intensity)"], + ["(param-ref 'laser1:intensity)\n0.666>"], + sep="\n" + ) as tm: + assert tm.laser1.intensity == 0.666 + + +def test_laser_mode_hop(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'laser1:charm:reg:mh-occured)"], + ['(param-ref \'laser1:charm:reg:mh-occured)\n#f>'], + sep="\n" + ) as tm: + assert tm.laser1.mode_hop == False + + +def test_laser_lock_start(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'laser1:charm:reg:started)"], + ['(param-ref \'laser1:charm:reg:started)\n""\r>'], + sep="\n" + ) as tm: + assert tm.laser1.lock_start is None + + +def test_laser_first_mode_hop_time(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'laser1:charm:reg:first-mh)"], + ['(param-ref \'laser1:charm:reg:first-mh)\n""\r>'], + sep="\n" + ) as tm: + assert tm.laser1.first_mode_hop_time is None + + +def test_laser_latest_mode_hop_time(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'laser1:charm:reg:latest-mh)"], + ['(param-ref \'laser1:charm:reg:latest-mh)\n""\r>'], + sep="\n" + ) as tm: + assert tm.laser1.latest_mode_hop_time is None + + +def test_laser_correction_status(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'laser1:charm:correction-status)"], + ['(param-ref \'laser1:charm:correction-status)\n0>'], + sep="\n" + ) as tm: + assert tm.laser1.correction_status == ik.toptica.TopMode.CharmStatus.un_initialized + + +def test_laser_correction(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'laser1:charm:correction-status)", "(exec 'laser1:charm:start-correction-initial)", + "(param-ref 'laser1:charm:correction-status)", "(exec 'laser1:charm:start-correction)"], + ['(param-ref \'laser1:charm:correction-status)\n0>', "(exec 'laser1:charm:start-correction-initial)\n>", + '(param-ref \'laser1:charm:correction-status)\n1>', "(exec 'laser1:charm:start-correction)\n>"], + sep="\n" + ) as tm: + tm.laser1.correction() + tm.laser1.correction() + +def test_reboot_system(): + with expected_protocol( + ik.toptica.TopMode, + ["(exec 'reboot-system)"], + ['(exec \'reboot-system)\n>'], + sep="\n" + ) as tm: + tm.reboot() + + +def test_laser_ontime(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'laser1:ontime)"], + ["(param-ref 'laser1:ontime)\n10000>"], + sep="\n" + ) as tm: + assert tm.laser1.on_time == 10000*pq\ + .s + + +def test_laser_charm_status(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'laser1:health)"], + ["(param-ref 'laser1:health)\n230>"], + sep="\n" + ) as tm: + assert tm.laser1.charm_status == 1 + + +def test_laser_temperature_control_status(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'laser1:health)"], + ["(param-ref 'laser1:health)\n230>"], + sep="\n" + ) as tm: + assert tm.laser1.temperature_control_status == 1 + + +def test_laser_current_control_status(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'laser1:health)"], + ["(param-ref 'laser1:health)\n230>"], + sep="\n" + ) as tm: + assert tm.laser1.current_control_status == 1 + + +def test_laser_production_date(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'laser1:production-date)"], + ["(param-ref 'laser1:production-date)\n2016-01-16>"], + sep="\n" + ) as tm: + assert tm.laser1.production_date == '2016-01-16' + + +def test_set_str(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-set! \'blo \"blee\")"], + ["(param-set! \'blo \"blee\")\n>"], + sep="\n" + ) as tm: + tm.set('blo', 'blee') + + +def test_set_list(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-set! \'blo \'(blee blo))"], + ["(param-set! \'blo \'(blee blo))\n>"], + sep="\n" + ) as tm: + tm.set('blo', ['blee','blo']) + + +def test_display(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-disp \'blo)"], + ["(param-disp \'blo)\n>bloop\n>"], + sep="\n" + ) as tm: + assert tm.display('blo') == "bloop" + + +def test_enable(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref \'emission)", "(param-set! \'enable-emission #f)"], + ["(param-ref \'emission)\n>#f\n", "(param-set! \'enable-emission #f)\n>"], + sep="\n" + ) as tm: + assert tm.enable == False + tm.enable = False + + +@raises(TypeError) +def test_enable_error(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-set! \'enable-emission #f)"], + ["(param-set! \'enable-emission #f)\n>"], + sep="\n" + ) as tm: + tm.enable = "False" + + +def test_front_key(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'front-key-locked)"], + ["(param-ref 'front-key-locked)\n#f>"], + sep="\n" + ) as tm: + assert tm.locked == False + + +def test_interlock(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'interlock-open)"], + ["(param-ref 'interlock-open)\n#f>"], + sep="\n" + ) as tm: + assert tm.interlock == False + + +def test_fpga_status(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'system-health)"], + ["(param-ref 'system-health)\n0>"], + sep="\n" + ) as tm: + assert tm.fpga_status == True + + +def test_temperature_status(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'system-health)"], + ["(param-ref 'system-health)\n2>"], + sep="\n" + ) as tm: + assert tm.temperature_status == False + + + +def test_current_status(): + with expected_protocol( + ik.toptica.TopMode, + ["(param-ref 'system-health)"], + ["(param-ref 'system-health)\n4>"], + sep="\n" + ) as tm: + assert tm.current_status == False \ No newline at end of file diff --git a/instruments/instruments/tests/test_util_fns.py b/instruments/instruments/tests/test_util_fns.py index d3053fdea..86295aa0b 100644 --- a/instruments/instruments/tests/test_util_fns.py +++ b/instruments/instruments/tests/test_util_fns.py @@ -31,7 +31,7 @@ from instruments.util_fns import ( ProxyList, - assume_units + assume_units, convert_temperature ) from flufl.enum import Enum @@ -134,7 +134,37 @@ def test_assume_units_correct(): # Check that raw scalars are made unitful. eq_(assume_units(1, 'm').rescale('mm').magnitude, 1000) - + +def test_temperature_conversion(): + blo = 70.0*pq.degF + out = convert_temperature(blo, pq.degC) + eq_(out.magnitude, 21.11111111111111) + out = convert_temperature(blo, pq.degK) + eq_(out.magnitude, 294.2055555555555) + out = convert_temperature(blo, pq.degF) + eq_(out.magnitude, 70.0) + + blo = 20.0*pq.degC + out = convert_temperature(blo, pq.degF) + eq_(out.magnitude, 68) + out = convert_temperature(blo, pq.degC) + eq_(out.magnitude, 20.0) + out = convert_temperature(blo, pq.degK) + eq_(out.magnitude, 293.15) + + blo = 270*pq.degK + out = convert_temperature(blo, pq.degC) + eq_(out.magnitude, -3.1499999999999773) + out = convert_temperature(blo, pq.degF) + eq_(out.magnitude, 141.94736842105263) + out = convert_temperature(blo, pq.K) + eq_(out.magnitude, 270) + +@raises(ValueError) +def test_temperater_conversion_failure(): + blo = 70.0*pq.degF + convert_temperature(blo, pq.V) + @raises(ValueError) def test_assume_units_failures(): assume_units(1, 'm').rescale('s') diff --git a/instruments/instruments/thorlabs/__init__.py b/instruments/instruments/thorlabs/__init__.py index c23d86ccf..7e034d60e 100644 --- a/instruments/instruments/thorlabs/__init__.py +++ b/instruments/instruments/thorlabs/__init__.py @@ -2,5 +2,6 @@ ThorLabsAPT, APTPiezoStage, APTStrainGaugeReader, APTMotorController ) from instruments.thorlabs.pm100usb import PM100USB -from instruments.thorlabs.lcc25 import LCC25 -from instruments.thorlabs.sc10 import SC10 +from instruments.thorlabs.lcc25 import LCC25, voltage_latch, slot_latch, check_cmd +from instruments.thorlabs.sc10 import SC10, trigger_check, check_time +from instruments.thorlabs.tc200 import TC200 diff --git a/instruments/instruments/thorlabs/lcc25.py b/instruments/instruments/thorlabs/lcc25.py index be67b0afb..dc1cda0fa 100644 --- a/instruments/instruments/thorlabs/lcc25.py +++ b/instruments/instruments/thorlabs/lcc25.py @@ -31,6 +31,45 @@ from instruments.abstract_instruments import Instrument from instruments.util_fns import assume_units + +def voltage_latch(newval): + """ + The voltage limits for the LCC25 are between 0 and 25 V + :param newval: the voltage to check + :type newval: float + :return: + """ + if newval < 0: + raise ValueError("Voltage is too low.") + if newval > 25: + raise ValueError("Voltage is too high") + + +def slot_latch(slot): + """ + Makes sure that the slot is within the number of defined values + :param slot: the slot number + :type slot: int + """ + if slot < 0: + raise ValueError("Slot number is less than 0") + if slot > 4: + raise ValueError("Slot number is greater than 4") + + +def check_cmd(response): + """ + Checks the for the two common Thorlabs error messages; CMD_NOT_DEFINED and CMD_ARG_INVALID + :param response: the response from the device + :return: 1 if not found, 0 otherwise + :rtype: int + """ + if response != "CMD_NOT_DEFINED" and response != "CMD_ARG_INVALID": + return 1 + else: + return 0 + + ## CLASSES ##################################################################### class LCC25(Instrument): @@ -45,12 +84,13 @@ class LCC25(Instrument): def __init__(self, filelike): super(LCC25, self).__init__(filelike) self.terminator = "\r" - self.end_terminator = ">" + self.prompt = ">" + self.echo = True ## ENUMS ## class Mode(IntEnum): - modulate = 0 + normal = 0 voltage1 = 1 voltage2 = 2 @@ -60,11 +100,7 @@ def name(self): """ gets the name and version number of the device """ - response = self.check_command("*idn?") - if response is "CMD_NOT_DEFINED": - self.name() - else: - return response + return self.query("*idn?") @property def frequency(self): @@ -74,17 +110,17 @@ def frequency(self): :units: As specified (if a `~quantities.Quantity`) or assumed to be of units Hertz. - :type: `~quantities.Quantity` + :rtype: `~quantities.Quantity` """ - response = self.check_command("freq?") - if not response is "CMD_NOT_DEFINED": - return float(response)*pq.Hz + response = self.query("freq?") + return float(response)*pq.Hz + @frequency.setter def frequency(self, newval): newval = assume_units(newval, pq.Hz).rescale(pq.Hz).magnitude if newval < 5: raise ValueError("Frequency is too low.") - if newval >150: + if newval > 150: raise ValueError("Frequency is too high") self.sendcmd("freq={}".format(newval)) @@ -93,18 +129,20 @@ def mode(self): """ Gets/sets the output mode of the LCC25 - :type: `LCC25.Mode` + :rtype: `LCC25.Mode` """ - response = self.check_command("mode?") - if not response is "CMD_NOT_DEFINED": - return LCC25.Mode[int(response)] + response = self.query("mode?") + return LCC25.Mode[int(response)] @mode.setter def mode(self, newval): - if (newval.enum is not LCC25.Mode): + if not hasattr(newval, 'enum'): + raise TypeError("Mode setting must be a `LCC25.Mode` value, " + "got {} instead.".format(type(newval))) + if newval.enum is not LCC25.Mode: raise TypeError("Mode setting must be a `LCC25.Mode` value, " "got {} instead.".format(type(newval))) - response = self.query("mode={}".format(newval.value)) + self.sendcmd("mode={}".format(newval.value)) @property def enable(self): @@ -113,11 +151,11 @@ def enable(self): If output enable is on (`True`), there is a voltage on the output. - :type: `bool` + :rtype: `bool` """ - response = self.check_command("enable?") - if not response is "CMD_NOT_DEFINED": - return True if int(response) is 1 else False + response = self.query("enable?") + return True if int(response) is 1 else False + @enable.setter def enable(self, newval): if not isinstance(newval, bool): @@ -133,11 +171,12 @@ def extern(self): Value is `True` for external TTL modulation and `False` for internal modulation. - :type: `bool` + :rtype: `bool` """ - response = self.check_command("extern?") + response = self.query("extern?") if not response is "CMD_NOT_DEFINED": return True if int(response) is 1 else False + @extern.setter def extern(self, newval): if not isinstance(newval, bool): @@ -153,11 +192,11 @@ def remote(self): Value is `False` for normal operation and `True` to lock out the front panel buttons. - :type: `bool` + :rtype: `bool` """ - response = self.check_command("remote?") - if not response is "CMD_NOT_DEFINED": - return True if int(response) is 1 else False + response = self.query("remote?") + return True if int(response) is 1 else False + @remote.setter def remote(self, newval): if not isinstance(newval, bool): @@ -172,18 +211,15 @@ def voltage1(self): :units: As specified (if a `~quantities.Quantity`) or assumed to be of units Volts. - :type: `~quantities.Quantity` + :rtype: `~quantities.Quantity` """ - response = self.check_command("volt1?") - if not response is "CMD_NOT_DEFINED": - return float(response)*pq.V + response = self.query("volt1?") + return float(response)*pq.V + @voltage1.setter def voltage1(self, newval): newval = assume_units(newval, pq.V).rescale(pq.V).magnitude - if newval < 0: - raise ValueError("Voltage is too low.") - if newval > 25: - raise ValueError("Voltage is too high") + voltage_latch(newval) self.sendcmd("volt1={}".format(newval)) @property @@ -193,18 +229,15 @@ def voltage2(self): :units: As specified (if a `~quantities.Quantity`) or assumed to be of units Volts. - :type: `~quantities.Quantity` + :rtype: `~quantities.Quantity` """ - response = self.check_command("volt2?") - if not response is "CMD_NOT_DEFINED": - return float(response)*pq.V + response = self.query("volt2?") + return float(response)*pq.V + @voltage2.setter def voltage2(self, newval): newval = assume_units(newval, pq.V).rescale(pq.V).magnitude - if newval < 0: - raise ValueError("Voltage is too low.") - if newval > 25: - raise ValueError("Voltage is too high") + voltage_latch(newval) self.sendcmd("volt2={}".format(newval)) @property @@ -214,18 +247,14 @@ def min_voltage(self): :units: As specified (if a `~quantities.Quantity`) or assumed to be of units Volts. - :type: `~quantities.Quantity` + :rtype: `~quantities.Quantity` """ - response = self.check_command("min?") - if not response is "CMD_NOT_DEFINED": - return float(response)*pq.V + response = self.query("min?") + return float(response)*pq.V + @min_voltage.setter def min_voltage(self, newval): newval = assume_units(newval, pq.V).rescale(pq.V).magnitude - if newval < 0: - raise ValueError("Voltage is too low.") - if newval > 25: - raise ValueError("Voltage is too high") self.sendcmd("min={}".format(newval)) @property @@ -236,19 +265,16 @@ def max_voltage(self): :units: As specified (if a `~quantities.Quantity`) or assumed to be of units Volts. - :type: `~quantities.Quantity` + :rtype: `~quantities.Quantity` """ - response = self.check_command("max?") - if not response is "CMD_NOT_DEFINED": - return float(response)*pq.V + response = self.query("max?") + return float(response)*pq.V + @max_voltage.setter def max_voltage(self, newval): newval = assume_units(newval, pq.V).rescale(pq.V).magnitude - if newval < 0: - raise ValueError("Voltage is too low.") - if newval > 25: - raise ValueError("Voltage is too high") + voltage_latch(newval) self.sendcmd("max={}".format(newval)) @property @@ -258,11 +284,11 @@ def dwell(self): :units: As specified (if a `~quantities.Quantity`) or assumed to be of units milliseconds. - :type: `~quantities.Quantity` + :rtype: `~quantities.Quantity` """ - response = self.check_command("dwell?") - if not response is "CMD_NOT_DEFINED": - return float(response)*pq.ms + response = self.query("dwell?") + return float(response)*pq.ms + @dwell.setter def dwell(self, newval): newval = int(assume_units(newval, pq.ms).rescale(pq.ms).magnitude) @@ -277,11 +303,11 @@ def increment(self): :units: As specified (if a `~quantities.Quantity`) or assumed to be of units Volts. - :type: `~quantities.Quantity` + :rtype: `~quantities.Quantity` """ - response = self.check_command("increment?") - if not response is "CMD_NOT_DEFINED": - return float(response)*pq.V + response = self.query("increment?") + return float(response)*pq.V + @increment.setter def increment(self, newval): newval = assume_units(newval, pq.V).rescale(pq.V).magnitude @@ -290,31 +316,6 @@ def increment(self, newval): self.sendcmd("increment={}".format(newval)) ## METHODS ## - - def check_command(self,command): - """ - Checks for the \"Command error CMD_NOT_DEFINED\" error, which can - sometimes occur if there were incorrect terminators on the previous - command. If the command is successful, it returns the value, if not, - it returns CMD_NOT_DEFINED - - check_command will also clear out the query string - """ - response = self.query(command) - response = self.read() - cmd_find = response.find("CMD_NOT_DEFINED") - if cmd_find ==-1: - error_find = response.find("CMD_ARG_INVALID") - if error_find ==-1: - output_str = response.replace(command,"") - output_str = output_str.replace(self.terminator,"") - output_str = output_str.replace(self.end_terminator,"") - else: - output_str = "CMD_ARG_INVALID" - else: - output_str = "CMD_NOT_DEFINED" - return output_str - def default(self): """ Restores instrument to factory settings. @@ -323,11 +324,8 @@ def default(self): :rtype: `int` """ - response = self.check_command("default") - if not response is "CMD_NOT_DEFINED": - return 1 - else: - return 0 + response = self.query("default") + return check_cmd(response) def save(self): """ @@ -337,11 +335,8 @@ def save(self): :rtype: `int` """ - response = self.check_command("save") - if not response is "CMD_NOT_DEFINED": - return 1 - else: - return 0 + response = self.query("save") + return check_cmd(response) def set_settings(self, slot): """ @@ -354,15 +349,9 @@ def set_settings(self, slot): :rtype: `int` """ - if slot < 0: - raise ValueError("Slot number is less than 0") - if slot > 4: - raise ValueError("Slot number is greater than 4") - response = self.check_command("set={}".format(slot)) - if response != "CMD_NOT_DEFINED" and response != "CMD_ARG_INVALID": - return 1 - else: - return 0 + slot_latch(slot) + response = self.query("set={}".format(slot)) + return check_cmd(response) def get_settings(self,slot): """ @@ -375,15 +364,9 @@ def get_settings(self,slot): :rtype: `int` """ - if slot < 0: - raise ValueError("Slot number is less than 0") - if slot > 4: - raise ValueError("Slot number is greater than 4") - response = self.check_command("get={}".format(slot)) - if response != "CMD_NOT_DEFINED" and response != "CMD_ARG_INVALID": - return 1 - else: - return 0 + slot_latch(slot) + response = self.query("get={}".format(slot)) + return check_cmd(response) def test_mode(self): """ @@ -395,8 +378,5 @@ def test_mode(self): :rtype: `int` """ - response = self.check_command("test") - if not response is "CMD_NOT_DEFINED": - return 1 - else: - return 0 + response = self.query("test") + return check_cmd(response) diff --git a/instruments/instruments/thorlabs/sc10.py b/instruments/instruments/thorlabs/sc10.py index e23a0e72f..288b2b7ef 100644 --- a/instruments/instruments/thorlabs/sc10.py +++ b/instruments/instruments/thorlabs/sc10.py @@ -30,8 +30,32 @@ from instruments.abstract_instruments import Instrument from instruments.util_fns import assume_units +from instruments.thorlabs import check_cmd + + +def trigger_check(newval): + """ + Validate the trigger + :param newval: trigger + :type newval: int + :return: + """ + if newval != 0 and newval != 1: + raise ValueError("Not a valid value for trigger mode") + + +def check_time(newval): + """ + Validate the shutter time + :param newval: the new time + :type newval: int + :return: + """ + if newval < 0: + raise ValueError("Duration cannot be negative") + if newval > 999999: + raise ValueError("Duration is too long") -## CLASSES ##################################################################### class SC10(Instrument): """ @@ -42,8 +66,8 @@ class SC10(Instrument): def __init__(self, filelike): super(SC10, self).__init__(filelike) self.terminator = '\r' - self.end_terminator = '>' - + self.prompt = '>' + self.echo = True ## ENUMS ## class Mode(IntEnum): @@ -55,16 +79,12 @@ class Mode(IntEnum): ## PROPERTIES ## - @property def name(self): """ Gets the name and version number of the device. """ - response = self.check_command("id?") - if response is "CMD_NOT_DEFINED": - self.name() - else: - return response + response = self.query("id?") + return response @property def enable(self): @@ -73,9 +93,9 @@ def enable(self): :type: `int` """ - response = self.check_command("ens?") - if not response is "CMD_NOT_DEFINED": - return int(response) + response = self.query("ens?") + return int(response) + @enable.setter def enable(self, newval): if newval == 0 or newval ==1: @@ -92,12 +112,12 @@ def repeat(self): :type: `int` """ - response = self.check_command("rep?") - if not response is "CMD_NOT_DEFINED": - return int(response) + response = self.query("rep?") + return int(response) + @repeat.setter def repeat(self, newval): - if newval >0 or newval <100: + if 0 < newval < 100: self.sendcmd("rep={}".format(newval)) self.read() else: @@ -111,12 +131,15 @@ def mode(self): :type: `SC10.Mode` """ - response = self.check_command("mode?") - if not response is "CMD_NOT_DEFINED": - return SC10.Mode[int(response)] + response = self.query("mode?") + return SC10.Mode[int(response)] + @mode.setter def mode(self, newval): - if (newval.enum is not SC10.Mode): + if not hasattr(newval, 'enum'): + raise TypeError("Mode setting must be a `SC10.Mode` value, " + "got {} instead.".format(type(newval))) + if newval.enum is not SC10.Mode: raise TypeError("Mode setting must be a `SC10.Mode` value, " "got {} instead.".format(type(newval))) @@ -132,13 +155,12 @@ def trigger(self): :type: `int` """ - response = self.check_command("trig?") - if not response is "CMD_NOT_DEFINED": - return int(response) + response = self.query("trig?") + return int(response) + @trigger.setter def trigger(self, newval): - if newval != 0 and newval != 1: - raise ValueError("Not a valid value for trigger mode") + trigger_check(newval) self.sendcmd("trig={}".format(newval)) self.read() @@ -152,15 +174,13 @@ def out_trigger(self): :type: `int` """ - response = self.check_command("xto?") - if not response is "CMD_NOT_DEFINED": - return int(response) + response = self.query("xto?") + return int(response) + @out_trigger.setter def out_trigger(self, newval): - if newval != 0 and newval != 1: - raise ValueError("Not a valid value for output trigger mode") + trigger_check(newval) self.sendcmd("xto={}".format(newval)) - self.read() ###I'm not sure how to handle checking for the number of digits yet. @property @@ -172,18 +192,14 @@ def open_time(self): of units milliseconds. :type: `~quantities.Quantity` """ - response = self.check_command("open?") - if not response is "CMD_NOT_DEFINED": - return float(response)*pq.ms + response = self.query("open?") + return float(response)*pq.ms + @open_time.setter def open_time(self, newval): newval = int(assume_units(newval, pq.ms).rescale(pq.ms).magnitude) - if newval < 0: - raise ValueError("Shutter open time cannot be negative") - if newval >999999: - raise ValueError("Shutter open duration is too long") + check_time(newval) self.sendcmd("open={}".format(newval)) - self.read() @property def shut_time(self): @@ -192,18 +208,15 @@ def shut_time(self): :units: As specified (if a `~quantities.Quantity`) or assumed to be of units milliseconds. - :type: `~quantities.Quantity` + :rtype: `~quantities.Quantity` """ - response = self.check_command("shut?") - if not response is "CMD_NOT_DEFINED": - return float(response)*pq.ms + response = self.query("shut?") + return float(response)*pq.ms + @shut_time.setter def shut_time(self, newval): newval = int(assume_units(newval, pq.ms).rescale(pq.ms).magnitude) - if newval < 0: - raise ValueError("Time cannot be negative") - if newval >999999: - raise ValueError("Duration is too long") + check_time(newval) self.sendcmd("shut={}".format(newval)) self.read() @@ -216,16 +229,15 @@ def baud_rate(self): :type: `int` """ - response = self.check_command("baud?") - if not response is "CMD_NOT_DEFINED": - return 115200 if response else 9600 + response = self.sendcmd("baud?") + return 115200 if response else 9600 + @baud_rate.setter def baud_rate(self, newval): - if newval != 9600 and newval !=115200: + if newval != 9600 and newval != 115200: raise ValueError("Invalid baud rate mode") else: self.sendcmd("baud={}".format(0 if newval == 9600 else 1)) - self.read() @property def closed(self): @@ -237,9 +249,8 @@ def closed(self): :rtype: `bool` """ - response = self.check_command("closed?") - if not response is "CMD_NOT_DEFINED": - return True if int(response) is 1 else False + response = self.query("closed?") + return True if int(response) is 1 else False @property def interlock(self): @@ -250,34 +261,10 @@ def interlock(self): :rtype: `bool` """ - response = self.check_command("interlock?") - if not response is "CMD_NOT_DEFINED": - return True if int(response) is 1 else False + response = self.query("interlock?") + return True if int(response) is 1 else False ## Methods ## - - def check_command(self,command): - """ - Checks for the \"Command error CMD_NOT_DEFINED\" error, which can sometimes occur if there were - incorrect terminators on the previous command. If the command is successful, it returns the value, - if not, it returns CMD_NOT_DEFINED - check_command will also clear out the query string - """ - response = self.query(command) - #This device will echo the commands sent, so another line must be read to catch the response. - response = self.read() - cmd_find = response.find("CMD_NOT_DEFINED") - if cmd_find ==-1: - error_find = response.find("CMD_ARG_INVALID") - if error_find ==-1: - output_str = response.replace(command,"") - output_str = output_str.replace(self.terminator,"") - output_str = output_str.replace(self.end_terminator,"") - else: - output_str = "CMD_ARG_INVALID" - else: - output_str = "CMD_NOT_DEFINED" - return output_str def default(self): """ @@ -287,11 +274,8 @@ def default(self): :rtype: `int` """ - response = self.check_command("default") - if not response is "CMD_NOT_DEFINED": - return 1 - else: - return 0 + response = self.query("default") + return check_cmd(response) def save(self): """ @@ -301,11 +285,8 @@ def save(self): :rtype: `int` """ - response = self.check_command("savp") - if not response is "CMD_NOT_DEFINED": - return 1 - else: - return 0 + response = self.query("savp") + return check_cmd(response) def save_mode(self): """ @@ -315,11 +296,8 @@ def save_mode(self): :rtype: `int` """ - response = self.check_command("save") - if not response is "CMD_NOT_DEFINED": - return 1 - else: - return 0 + response = self.query("save") + return check_cmd(response) def restore(self): """ @@ -329,8 +307,5 @@ def restore(self): :rtype: `int` """ - response = self.check_command("resp") - if not response is "CMD_NOT_DEFINED": - return 1 - else: - return 0 + response = self.query("resp") + return check_cmd(response) \ No newline at end of file diff --git a/instruments/instruments/thorlabs/tc200.py b/instruments/instruments/thorlabs/tc200.py new file mode 100644 index 000000000..e0045dbab --- /dev/null +++ b/instruments/instruments/thorlabs/tc200.py @@ -0,0 +1,307 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +## +# tc200.py: class for the Thorlabs TC200 Temperature Controller +## +# © 2014 Steven Casagrande (scasagrande@galvant.ca). +# +# This file is a part of the InstrumentKit project. +# Licensed under the AGPL version 3. +## +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +## +# TC200 Class contributed by Catherine Holloway +# +## IMPORTS ##################################################################### + +import quantities as pq +from flufl.enum import IntEnum + +from instruments.abstract_instruments import Instrument +from instruments.util_fns import assume_units, convert_temperature + +## CLASSES ##################################################################### + + +class TC200(Instrument): + """ + The TC200 is is a controller for the voltage across a heating element. It can also read in the temperature off of a + thermistor and implements a PID control to keep the temperature at a set value. + The user manual can be found here: + http://www.thorlabs.com/thorcat/12500/TC200-Manual.pdf + """ + def __init__(self, filelike): + super(TC200, self).__init__(filelike) + self.terminator = "\r" + self.prompt = ">" + self.echo = True + + ## ENUMS ## + + class Mode(IntEnum): + normal = 0 + cycle = 1 + + class Sensor(IntEnum): + ptc100 = 0 + ptc1000 = 1 + th10k = 2 + ntc10k = 3 + + ## PROPERTIES ## + + def name(self): + """ + gets the name and version number of the device + :return: the name string of the device + :rtype: str + """ + response = self.query("*idn?") + return response + + @property + def mode(self): + """ + Gets/sets the output mode of the TC200 + + :type: `TC200.Mode` + """ + response = self.query("stat?") + response_code = (int(response) << 1) % 2 + return TC200.Mode[response_code] + + @mode.setter + def mode(self, newval): + if not hasattr(newval, 'enum'): + raise TypeError("Mode setting must be a `TC200.Mode` value, " + "got {} instead.".format(type(newval))) + if newval.enum is not TC200.Mode: + raise TypeError("Mode setting must be a `TC200.Mode` value, " + "got {} instead.".format(type(newval))) + out_query = "mode={}".format(newval.name) + self.query(out_query) + + @property + def enable(self): + """ + Gets/sets the heater enable status. + + If output enable is on (`True`), there is a voltage on the output. + + :rtype: `bool` + """ + response = self.query("stat?") + return True if int(response) % 2 is 1 else False + + @enable.setter + def enable(self, newval): + if not isinstance(newval, bool): + raise TypeError("TC200 enable property must be specified with a " + "boolean.") + # the "ens" command is a toggle, we need to track two different cases, when it should be on and it is off, + # and when it is off and should be on + if newval and not self.enable: + self.query("ens") + elif not newval and self.enable: + self.query("ens") + + @property + def temperature(self): + """ + Gets/sets the temperature + + :return: the temperature (in degrees C) + :rtype: float + """ + response = self.query("tact?").replace(" C", "").replace(" F", "").replace(" K", "") + return float(response)*pq.degC + + @temperature.setter + def temperature(self, newval): + # the set temperature is always in celsius + newval = convert_temperature(newval, pq.degC).magnitude + if newval < 20.0 or newval > self.max_temperature: + raise ValueError("Temperature is out of range.") + out_query = "tset={}".format(newval) + self.query(out_query) + + @property + def p(self): + """ + Gets/sets the pgain + + :return: the gain (in nnn) + :rtype: int + """ + response = self.query("pid?") + return int(response.split(" ")[0]) + + @p.setter + def p(self, newval): + if newval < 1: + raise ValueError("P-Value is too low.") + if newval > 250: + raise ValueError("P-Value is too high") + self.query("pgain={}".format(newval)) + + @property + def i(self): + """ + Gets/sets the igain + + :return: the gain + :rtype: int + """ + response = self.query("pid?") + return int(response.split(" ")[1]) + + @i.setter + def i(self, newval): + if newval < 0: + raise ValueError("I-Value is too low.") + if newval > 250: + raise ValueError("I-Value is too high") + self.query("igain={}".format(newval)) + + @property + def d(self): + """ + Gets/sets the dgain + + :return: the gain (in nnn) + :rtype: int + """ + response = self.query("pid?") + return int(response.split(" ")[2]) + + @d.setter + def d(self, newval): + if newval < 0: + raise ValueError("D-Value is too low.") + if newval > 250: + raise ValueError("D-Value is too high") + self.query("dgain={}".format(newval)) + + @property + def degrees(self): + """ + Gets/sets the mode of the temperature measurement. + + :type: `~quantities.unitquantity.UnitTemperature` + """ + response = self.query("stat?") + response = int(response) + if (response >> 4) % 2 and (response >> 5) % 2: + return pq.degC + elif (response >> 5) % 2: + return pq.degK + else: + return pq.degF + + @degrees.setter + def degrees(self, newval): + if newval is pq.degC: + self.query("unit=c") + elif newval is pq.degF: + self.query("unit=f") + elif newval is pq.degK: + self.query("unit=k") + else: + raise TypeError("Invalid temperature type") + + @property + def sensor(self): + """ + Gets/sets the current thermistor type. Used for converting resistances to temperatures. + + :rtype: TC200.Sensor + + """ + response = self.query("sns?") + response = response.split(",")[0].replace("Sensor = ", '').replace(self.terminator, "").replace(" ", "") + return TC200.Sensor(response.lower()) + + @sensor.setter + def sensor(self, newval): + if not hasattr(newval, 'enum'): + raise TypeError("Sensor setting must be a `TC200.Sensor` value, " + "got {} instead.".format(type(newval))) + + if newval.enum is not TC200.Sensor: + raise TypeError("Sensor setting must be a `TC200.Sensor` value, " + "got {} instead.".format(type(newval))) + self.query("sns={}".format(newval.name)) + + @property + def beta(self): + """ + Gets/sets the beta value of the thermistor curve. + + :return: the gain (in nnn) + :rtype: int + """ + response = self.query("beta?") + return int(response) + + @beta.setter + def beta(self, newval): + if newval < 2000: + raise ValueError("Beta Value is too low.") + if newval > 6000: + raise ValueError("Beta Value is too high") + self.query("beta={}".format(newval)) + + @property + def max_power(self): + """ + Gets/sets the maximum power + + :return: the maximum power (in Watts) + :rtype: `~quantities.Quantity` + """ + response = self.query("pmax?") + return float(response)*pq.W + + @max_power.setter + def max_power(self, newval): + newval = assume_units(newval, pq.W).rescale(pq.W).magnitude + if newval < 0.1: + raise ValueError("Power is too low.") + if newval > 18.0: + raise ValueError("Power is too high") + self.query("PMAX={}".format(newval)) + + @property + def max_temperature(self): + """ + Gets/sets the maximum temperature + + :return: the maximum temperature (in deg C) + :rtype: `~quantities.Quantity` + """ + response = self.query("tmax?").replace(" C","") + return float(response)*pq.degC + + @max_temperature.setter + def max_temperature(self, newval): + newval = convert_temperature(newval, pq.degC).magnitude + if newval < 20: + raise ValueError("Temperature is too low.") + if newval > 205.0: + raise ValueError("Temperature is too high") + self.query("TMAX={}".format(newval)) + + # The Cycles functionality of the TC200 is currently unimplemented, as it is complex, and its functionality is + # redundant given a python interface to TC200 + diff --git a/instruments/instruments/toptica/__init__.py b/instruments/instruments/toptica/__init__.py new file mode 100644 index 000000000..815ffb7f3 --- /dev/null +++ b/instruments/instruments/toptica/__init__.py @@ -0,0 +1 @@ +from instruments.toptica.topmode import TopMode, convert_toptica_boolean, convert_toptica_datetime \ No newline at end of file diff --git a/instruments/instruments/toptica/topmode.py b/instruments/instruments/toptica/topmode.py new file mode 100644 index 000000000..0a0b0c69a --- /dev/null +++ b/instruments/instruments/toptica/topmode.py @@ -0,0 +1,251 @@ +import quantities as pq +from flufl.enum import IntEnum +from datetime import datetime + +from instruments.abstract_instruments import Instrument + + +def convert_toptica_boolean(response): + """ + Converts the toptica boolean expression to a boolean + :param response: response string + :type response: str + :return: the converted boolean + :rtype: bool + """ + if response.find('Error: -3') > -1: + return None + elif response.find('f') > -1: + return False + elif response.find('t') > -1: + return True + else: + raise ValueError("cannot convert: "+str(response)+" to boolean") + + +def convert_toptica_datetime(response): + """ + Converts the toptical date format to a python time date + :param response: the string from the topmode + :type response: str + :return: the converted date + :rtype: 'datetime.datetime' + """ + if response == '""\r': + return None + else: + return datetime.strptime(response, '%b %d %Y %I:%M%p') + + +class TopMode(Instrument): + """ + The TopMode is a diode laser with active stabilization, produced by Toptica. + The spec sheet is available here: + http://www.toptica.com/fileadmin/user_upload/products/Diode_Lasers/Industrial_OEM/Single_Frequency/TopMode/toptica_BR_TopMode.pdf + """ + class CharmStatus(IntEnum): + un_initialized = 0 + in_progress = 1 + success = 2 + failure = 3 + + class Laser(object): + """ + Since the topmode can have multiple lasers attached to it, a laser object will be defined. + """ + def __init__(self, number=1, parent=None): + self.number = number + self.parent = parent + self.name = "laser"+str(self.number) + + @property + def serial_number(self): + return self.parent.reference(self.name+":serial-number") + + @property + def model(self): + return self.parent.reference(self.name+":model") + + @property + def wavelength(self): + return float(self.parent.reference(self.name+":wavelength"))*pq.nm + + @property + def production_date(self): + return self.parent.reference(self.name+":production-date") + + @property + def enable(self): + return convert_toptica_boolean(self.parent.reference(self.name+":emission")) + + @enable.setter + def enable(self, newval): + if type(newval) is not bool: + raise TypeError("Laser emmission must be a boolean, got: "+type(newval)) + return self.parent.set(self.name+":enable-emission", newval) + + @property + def on_time(self): + return float(self.parent.reference(self.name+":ontime"))*pq.s + + @property + def charm_status(self): + response = int(self.parent.reference(self.name+":health")) + return (response >> 7) % 2 == 1 + + @property + def temperature_control_status(self): + response = int(self.parent.reference(self.name+":health")) + return (response >> 5) % 2 == 1 + + @property + def current_control_status(self): + response = int(self.parent.reference(self.name+":health")) + return (response >> 6) % 2 == 1 + + @property + def tec_status(self): + return convert_toptica_boolean(self.parent.reference(self.name+":tec:ready")) + + @property + def intensity(self): + """ + This parameter is unitless + """ + return float(self.parent.reference(self.name+":intensity")) + + @property + def mode_hop(self): + """ + Checks whether the laser has mode-hopped + """ + return convert_toptica_boolean(self.parent.reference(self.name+":charm:reg:mh-occured")) + + @property + def lock_start(self): + """ + Returns the date and time of the start of mode-locking + """ + return convert_toptica_datetime(self.parent.reference(self.name+":charm:reg:started")) + + @property + def first_mode_hop_time(self): + """ + Returns the date and time of the first mode hop + """ + return convert_toptica_datetime(self.parent.reference(self.name+":charm:reg:first-mh")) + + @property + def latest_mode_hop_time(self): + """ + Returns the date and time of the latest mode hop + """ + return convert_toptica_datetime(self.parent.reference(self.name+":charm:reg:latest-mh")) + + @property + def correction_status(self): + return TopMode.CharmStatus[int(self.parent.reference(self.name+":charm:correction-status"))] + + def correction(self): + """ + run the correction + """ + if self.correction_status == TopMode.CharmStatus.un_initialized: + self.parent.execute(self.name+":charm:start-correction-initial") + else: + self.parent.execute(self.name+":charm:start-correction") + + + def __init__(self, filelike): + super(TopMode, self).__init__(filelike) + self.prompt = ">" + self.terminator = "\n" + self.echo = True + self.laser1 = TopMode.Laser(1, self) + self.laser2 = TopMode.Laser(2, self) + + # The TopMode has its own control language, here we define each command individually: + def execute(self, command): + self.query("(exec '"+command+")") + + def set(self, param, value): + if type(value) is str: + self.sendcmd("(param-set! '"+param+" \""+value+"\")") + elif type(value) is tuple or type(value) is list: + self.sendcmd("(param-set! '"+param+" '("+" ".join(value)+"))") + elif type(value) is bool: + out_str = "t" if value else "f" + self.sendcmd("(param-set! '"+param+" #"+out_str+")") + + def reference(self, param): + return self.query("(param-ref '"+param+")") + + def display(self, param): + return self.query("(param-disp '"+param+")") + + @property + def enable(self): + """ + is the laser lasing? + :return: + """ + return convert_toptica_boolean(self.reference("emission")) + + @enable.setter + def enable(self, newenable): + if type(newenable) is not bool: + raise TypeError("Emission status must be a boolean, got: "+str(type(newenable))) + self.set("enable-emission", newenable) + + @property + def locked(self): + """ + Is the key switch unlocked? + :return: + """ + return convert_toptica_boolean(self.reference("front-key-locked")) + + @property + def interlock(self): + """ + Is the interlock switch open? + :return: + """ + return convert_toptica_boolean(self.reference("interlock-open")) + + @property + def fpga_status(self): + """ + returns false on FPGA failure + :return: + """ + response = int(self.reference("system-health")) + return False if response % 2 else True + + @property + def temperature_status(self): + """ + returns false if there is a temperature controller board failure + :return: + """ + response = int(self.reference("system-health")) + return False if (response >> 1) % 2 else True + + @property + def current_status(self): + """ + returns false if there is a current controller board failure + :return: + """ + response = int(self.reference("system-health")) + return False if (response >> 2) % 2 else True + + def reboot(self): + """ + Reboots the system (note, this will end the serial connection) + """ + self.execute("reboot-system") + + + ## Network configuration is unimplemented + diff --git a/instruments/instruments/util_fns.py b/instruments/instruments/util_fns.py index a46362d3b..f18f1803d 100644 --- a/instruments/instruments/util_fns.py +++ b/instruments/instruments/util_fns.py @@ -54,6 +54,41 @@ def assume_units(value, units): value = pq.Quantity(value, units) return value + +def convert_temperature(temperature, base): + """ + convert the temperature to the specified base + :param temperature: a quantity with units of Kelvin, Celsius, or Fahrenheit + :type temperature: `quantities.Quantity` + :param base: a temperature unit to convert to + :type base: `unitquantity.UnitTemperature` + :return: the converted temperature + :rtype: `quantities.Quantity` + """ + # quantities reports equivalence between degC and degK, so a string comparison is needed + newval = assume_units(temperature, pq.degC) + if newval.units == pq.degF and str(base).split(" ")[1] == 'degC': + return ((newval.magnitude-32.0)*5.0/9.0)*base + elif str(newval.units).split(" ")[1] == 'K' and str(base).split(" ")[1] == 'degC': + return (newval.magnitude-273.15)*base + elif str(newval.units).split(" ")[1] == 'K' and base == pq.degF: + return (newval.magnitude/1.8-459/57)*base + elif str(newval.units).split(" ")[1] == 'degC' and base == pq.degF: + return (newval.magnitude*9.0/5.0+32.0)*base + elif newval.units == pq.degF and str(base).split(" ")[1] == 'K': + return ((newval.magnitude+459.57)*5.0/9.0)*base + elif str(newval.units).split(" ")[1] == 'degC' and str(base).split(" ")[1] == 'K': + return (newval.magnitude+273.15)*base + elif str(newval.units).split(" ")[1] == 'degC' and str(base).split(" ")[1] == 'degC': + return newval + elif newval.units == pq.degF and base == pq.degF: + return newval + elif str(newval.units).split(" ")[1] == 'K' and str(base).split(" ")[1] == 'K': + return newval + else: + raise ValueError("Unable to convert "+str(newval.units)+" to "+str(base)) + + def split_unit_str(s, default_units=pq.dimensionless, lookup=None): """ Given a string of the form "12 C" or "14.7 GHz", returns a tuple of the diff --git a/instruments/setup.py b/instruments/setup.py index 5ec8c9169..b26d0acbc 100644 --- a/instruments/setup.py +++ b/instruments/setup.py @@ -41,6 +41,7 @@ 'instruments.srs', 'instruments.tektronix', 'instruments.thorlabs', + 'instruments.toptica', 'instruments.yokogawa', ], install_requires = [