From 22ba8ba55799b62b77c69c78db61fb8eee7516bf Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 6 Jan 2017 15:17:00 +0000 Subject: [PATCH 01/72] Rename plankton_emulators to lewis_emulators because framework name has changed --- lewis_emulators/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 lewis_emulators/__init__.py diff --git a/lewis_emulators/__init__.py b/lewis_emulators/__init__.py new file mode 100644 index 0000000..3be6bd2 --- /dev/null +++ b/lewis_emulators/__init__.py @@ -0,0 +1 @@ +from __future__ import absolute_import \ No newline at end of file From 6f425de01349e9d00b5fb38db15cf1a25d186989 Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Thu, 1 Jun 2017 09:49:18 +0100 Subject: [PATCH 02/72] Update __init__.py --- lewis_emulators/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/__init__.py b/lewis_emulators/__init__.py index 3be6bd2..c396168 100644 --- a/lewis_emulators/__init__.py +++ b/lewis_emulators/__init__.py @@ -1 +1 @@ -from __future__ import absolute_import \ No newline at end of file +from __future__ import absolute_import From 91fdfb655a8b084a0eff00c4b7c66de22c34696c Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Thu, 8 Jun 2017 12:28:08 +0100 Subject: [PATCH 03/72] Tidied up the formatting for a lot of files --- lewis_emulators/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/__init__.py b/lewis_emulators/__init__.py index 3be6bd2..c396168 100644 --- a/lewis_emulators/__init__.py +++ b/lewis_emulators/__init__.py @@ -1 +1 @@ -from __future__ import absolute_import \ No newline at end of file +from __future__ import absolute_import From 815d3109120668a38dea2c52832c2367c70adbe1 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 25 Sep 2017 12:25:41 +0100 Subject: [PATCH 04/72] Pin emulators to latest version --- lewis_emulators/lewis_versions.py | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 lewis_emulators/lewis_versions.py diff --git a/lewis_emulators/lewis_versions.py b/lewis_emulators/lewis_versions.py new file mode 100644 index 0000000..79a658b --- /dev/null +++ b/lewis_emulators/lewis_versions.py @@ -0,0 +1,2 @@ +LEWIS_1_1_1 = "1.1.1" +LEWIS_LATEST = LEWIS_1_1_1 \ No newline at end of file From a3ba86182e70fb6c874acce5fd98dd1f082878f8 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 19 Dec 2017 09:21:23 +0000 Subject: [PATCH 05/72] Add device to test framework --- tests/triton.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/triton.py diff --git a/tests/triton.py b/tests/triton.py new file mode 100644 index 0000000..8bb9c84 --- /dev/null +++ b/tests/triton.py @@ -0,0 +1,18 @@ +import unittest +from unittest import skipIf + +from utils.channel_access import ChannelAccess +from utils.ioc_launcher import IOCRegister +from utils.testing import get_running_lewis_and_ioc + + +class TritonTests(unittest.TestCase): + """ + Tests for the Triton IOC. + """ + def setUp(self): + self._lewis, self._ioc = get_running_lewis_and_ioc("Triton") + self.ca = ChannelAccess(device_prefix="TRITON_01") + + def test_that_fails(self): + self.fail("You haven't implemented any tests!") From 721625fe78c699e0f5795fc81611cc9f5755bf47 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 19 Dec 2017 09:21:40 +0000 Subject: [PATCH 06/72] Add template emulator --- lewis_emulators/triton/__init__.py | 5 ++++ lewis_emulators/triton/device.py | 25 +++++++++++++++++++ lewis_emulators/triton/interfaces/__init__.py | 3 +++ .../triton/interfaces/stream_interface.py | 12 +++++++++ lewis_emulators/triton/states.py | 5 ++++ 5 files changed, 50 insertions(+) create mode 100644 lewis_emulators/triton/__init__.py create mode 100644 lewis_emulators/triton/device.py create mode 100644 lewis_emulators/triton/interfaces/__init__.py create mode 100644 lewis_emulators/triton/interfaces/stream_interface.py create mode 100644 lewis_emulators/triton/states.py diff --git a/lewis_emulators/triton/__init__.py b/lewis_emulators/triton/__init__.py new file mode 100644 index 0000000..c68adeb --- /dev/null +++ b/lewis_emulators/triton/__init__.py @@ -0,0 +1,5 @@ +from .device import SimulatedTriton +from ..lewis_versions import LEWIS_LATEST + +framework_version = LEWIS_LATEST +__all__ = ['SimulatedTriton'] diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py new file mode 100644 index 0000000..1c3765d --- /dev/null +++ b/lewis_emulators/triton/device.py @@ -0,0 +1,25 @@ +from collections import OrderedDict +from states import DefaultState +from lewis.devices import StateMachineDevice + + +class SimulatedTriton(StateMachineDevice): + + def _initialize_data(self): + """ + Initialize all of the device's attributes. + """ + pass + + def _get_state_handlers(self): + return { + 'default': DefaultState(), + } + + def _get_initial_state(self): + return 'default' + + def _get_transition_handlers(self): + return OrderedDict([ + ]) + diff --git a/lewis_emulators/triton/interfaces/__init__.py b/lewis_emulators/triton/interfaces/__init__.py new file mode 100644 index 0000000..605c573 --- /dev/null +++ b/lewis_emulators/triton/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import TritonStreamInterface + +__all__ = ['TritonStreamInterface'] diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py new file mode 100644 index 0000000..e08e7ba --- /dev/null +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -0,0 +1,12 @@ +from lewis.adapters.stream import StreamInterface, Cmd + + +class TritonStreamInterface(StreamInterface): + + # Commands that we expect via serial during normal operation + commands = { + Cmd("catch_all", "^#9.*$"), # Catch-all command for debugging + } + + def catch_all(self): + pass diff --git a/lewis_emulators/triton/states.py b/lewis_emulators/triton/states.py new file mode 100644 index 0000000..e4ca48e --- /dev/null +++ b/lewis_emulators/triton/states.py @@ -0,0 +1,5 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + pass From ee39e0a8b13c0e2597c8a65294bf203a77c09ca3 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 19 Dec 2017 11:09:28 +0000 Subject: [PATCH 07/72] Add triton tests --- tests/triton.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/triton.py b/tests/triton.py index 8bb9c84..cce2ef4 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -5,14 +5,20 @@ from utils.ioc_launcher import IOCRegister from utils.testing import get_running_lewis_and_ioc +DEVICE_PREFIX = "TRITON_01" + class TritonTests(unittest.TestCase): """ Tests for the Triton IOC. """ def setUp(self): - self._lewis, self._ioc = get_running_lewis_and_ioc("Triton") - self.ca = ChannelAccess(device_prefix="TRITON_01") + self._lewis, self._ioc = get_running_lewis_and_ioc("triton") + self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX) + + def test_WHEN_device_is_started_THEN_it_is_not_disabled(self): + self.ca.assert_that_pv_is("DISABLE", "COMMS ENABLED") - def test_that_fails(self): - self.fail("You haven't implemented any tests!") + @skipIf(IOCRegister.uses_rec_sim, "Not implemented in recsim") + def test_WHEN_device_is_started_THEN_can_get_mixing_chamber_uid(self): + self.ca.assert_that_pv_is("MC:UID", "mix_chamber_name") From 06660f0f2eb38bac6f71fd442408d4660a056ed5 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 19 Dec 2017 11:09:57 +0000 Subject: [PATCH 08/72] Add correct terminators and one command to stream interface --- lewis_emulators/triton/interfaces/stream_interface.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index e08e7ba..7c07b2d 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -5,8 +5,11 @@ class TritonStreamInterface(StreamInterface): # Commands that we expect via serial during normal operation commands = { - Cmd("catch_all", "^#9.*$"), # Catch-all command for debugging + Cmd("get_mc_uid", "^READ:SYS:DR:CHAN:MC$"), # Catch-all command for debugging } - def catch_all(self): - pass + in_terminator = "\r\n" + out_terminator = "\r\n" + + def get_mc_uid(self): + return "STAT:SYS:DR:CHAN:MC:{}".format("mix_chamber_name") From 798fe8cc7f69e493b2900710d044dbcf845e8bba Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 19 Dec 2017 12:40:56 +0000 Subject: [PATCH 09/72] PID controls working --- lewis_emulators/triton/device.py | 26 ++++++++++++- .../triton/interfaces/stream_interface.py | 37 ++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 1c3765d..5280cdb 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -3,13 +3,20 @@ from lewis.devices import StateMachineDevice +SUBSYSTEM_NAMES = { + "mixing chamber": "mix_chamber_name", + } + + class SimulatedTriton(StateMachineDevice): def _initialize_data(self): """ Initialize all of the device's attributes. """ - pass + self.p = 0 + self.i = 0 + self.d = 0 def _get_state_handlers(self): return { @@ -23,3 +30,20 @@ def _get_transition_handlers(self): return OrderedDict([ ]) + def set_p(self, value): + self.p = value + + def set_i(self, value): + self.i = value + + def set_d(self, value): + self.d = value + + def get_p(self): + return self.p + + def get_i(self): + return self.i + + def get_d(self): + return self.d diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 7c07b2d..96d7048 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -1,15 +1,48 @@ from lewis.adapters.stream import StreamInterface, Cmd +from lewis_emulators.utils.command_builder import CmdBuilder +from lewis_emulators.triton.device import SUBSYSTEM_NAMES class TritonStreamInterface(StreamInterface): # Commands that we expect via serial during normal operation commands = { - Cmd("get_mc_uid", "^READ:SYS:DR:CHAN:MC$"), # Catch-all command for debugging + CmdBuilder("get_mc_uid").escape("READ:SYS:DR:CHAN:MC").build(), + + # PID setpoints + CmdBuilder("set_p").escape("SET:DEV:{}:TEMP:LOOP:P:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + CmdBuilder("set_i").escape("SET:DEV:{}:TEMP:LOOP:I:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + CmdBuilder("set_d").escape("SET:DEV:{}:TEMP:LOOP:D:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + + # PID readbacks + CmdBuilder("get_p").escape("READ:DEV:{}:TEMP:LOOP:P".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + CmdBuilder("get_i").escape("READ:DEV:{}:TEMP:LOOP:I".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + CmdBuilder("get_d").escape("READ:DEV:{}:TEMP:LOOP:D".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), } in_terminator = "\r\n" out_terminator = "\r\n" def get_mc_uid(self): - return "STAT:SYS:DR:CHAN:MC:{}".format("mix_chamber_name") + return "STAT:SYS:DR:CHAN:MC:{}".format(SUBSYSTEM_NAMES["mixing chamber"]) + + def set_p(self, value): + self.device.set_p(value) + return "ok" + + def set_i(self, value): + self.device.set_i(value) + return "ok" + + def set_d(self, value): + self.device.set_d(value) + return "ok" + + def get_p(self): + return "STAT:DEV:{}:TEMP:LOOP:P:{}".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_p()) + + def get_i(self): + return "STAT:DEV:{}:TEMP:LOOP:I:{}".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_i()) + + def get_d(self): + return "STAT:DEV:{}:TEMP:LOOP:D:{}".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_d()) From d177f0fd40b916c88a415137c543da36a2de2ad9 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 19 Dec 2017 12:41:14 +0000 Subject: [PATCH 10/72] PID controls working --- tests/triton.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/triton.py b/tests/triton.py index cce2ef4..f3a1128 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -22,3 +22,12 @@ def test_WHEN_device_is_started_THEN_it_is_not_disabled(self): @skipIf(IOCRegister.uses_rec_sim, "Not implemented in recsim") def test_WHEN_device_is_started_THEN_can_get_mixing_chamber_uid(self): self.ca.assert_that_pv_is("MC:UID", "mix_chamber_name") + + def test_WHEN_P_setpoint_is_set_THEN_readback_updates(self): + self.ca.assert_setting_setpoint_sets_readback(5, "P") + + def test_WHEN_I_setpoint_is_set_THEN_readback_updates(self): + self.ca.assert_setting_setpoint_sets_readback(5, "I") + + def test_WHEN_D_setpoint_is_set_THEN_readback_updates(self): + self.ca.assert_setting_setpoint_sets_readback(5, "D") From c3eaf9de59976a14ef919e6d9a4ac52212be841c Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 19 Dec 2017 14:16:31 +0000 Subject: [PATCH 11/72] Add heater range/temp setpoint --- lewis_emulators/triton/device.py | 15 +++++++++++ .../triton/interfaces/stream_interface.py | 26 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 5280cdb..0486322 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -14,6 +14,9 @@ def _initialize_data(self): """ Initialize all of the device's attributes. """ + self.temperature_setpoint = 0 + self.heater_range = 0 + self.p = 0 self.i = 0 self.d = 0 @@ -47,3 +50,15 @@ def get_i(self): def get_d(self): return self.d + + def get_temperature_setpoint(self): + return self.temperature_setpoint + + def set_temperature_setpoint(self, value): + self.temperature_setpoint = value + + def get_heater_range(self): + return self.heater_range + + def set_heater_range(self, value): + self.heater_range = value diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 96d7048..db94a1b 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -18,6 +18,18 @@ class TritonStreamInterface(StreamInterface): CmdBuilder("get_p").escape("READ:DEV:{}:TEMP:LOOP:P".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), CmdBuilder("get_i").escape("READ:DEV:{}:TEMP:LOOP:I".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), CmdBuilder("get_d").escape("READ:DEV:{}:TEMP:LOOP:D".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + + # Setpoint temperature + CmdBuilder("set_temperature_setpoint") + .escape("SET:DEV:{}:TEMP:LOOP:TSET:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + CmdBuilder("get_temperature_setpoint") + .escape("READ:DEV:{}:TEMP:LOOP:TSET".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + + # Heater range + CmdBuilder("set_heater_range") + .escape("SET:DEV:{}:TEMP:LOOP:RANGE:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + CmdBuilder("get_heater_range") + .escape("READ:DEV:{}:TEMP:LOOP:RANGE".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), } in_terminator = "\r\n" @@ -46,3 +58,17 @@ def get_i(self): def get_d(self): return "STAT:DEV:{}:TEMP:LOOP:D:{}".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_d()) + + def set_temperature_setpoint(self, value): + self.device.set_temperature_setpoint(value) + + def get_temperature_setpoint(self): + return "STAT:DEV:{}:TEMP:LOOP:TSET:{}K".format(SUBSYSTEM_NAMES["mixing chamber"], + self.device.get_temperature_setpoint()) + + def set_heater_range(self, value): + self.device.set_heater_range(value) + + def get_heater_range(self): + return "STAT:DEV:{}:TEMP:LOOP:RANGE:{}".format(SUBSYSTEM_NAMES["mixing chamber"], + self.device.get_heater_range()) From 5e9999bde597ee95786eb137fd981b8da38c9c2d Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 19 Dec 2017 14:16:48 +0000 Subject: [PATCH 12/72] Add heater range/temp setpoint --- tests/triton.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/triton.py b/tests/triton.py index f3a1128..0c0ead8 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -7,6 +7,10 @@ DEVICE_PREFIX = "TRITON_01" +PID_TEST_VALUES = 0, 10**-5, 123.45, 10**5 +TEMPERATURE_TEST_VALUES = 0, 10**-5, 5.4321, 1000 +HEATER_RANGE_TEST_VALUES = 0.001, 0.316, 1000 + class TritonTests(unittest.TestCase): """ @@ -24,10 +28,21 @@ def test_WHEN_device_is_started_THEN_can_get_mixing_chamber_uid(self): self.ca.assert_that_pv_is("MC:UID", "mix_chamber_name") def test_WHEN_P_setpoint_is_set_THEN_readback_updates(self): - self.ca.assert_setting_setpoint_sets_readback(5, "P") + for value in PID_TEST_VALUES: + self.ca.assert_setting_setpoint_sets_readback(value, "P") def test_WHEN_I_setpoint_is_set_THEN_readback_updates(self): - self.ca.assert_setting_setpoint_sets_readback(5, "I") + for value in PID_TEST_VALUES: + self.ca.assert_setting_setpoint_sets_readback(value, "I") def test_WHEN_D_setpoint_is_set_THEN_readback_updates(self): - self.ca.assert_setting_setpoint_sets_readback(5, "D") + for value in PID_TEST_VALUES: + self.ca.assert_setting_setpoint_sets_readback(value, "D") + + def test_WHEN_temperature_setpoint_is_set_THEN_readback_updates(self): + for value in TEMPERATURE_TEST_VALUES: + self.ca.assert_setting_setpoint_sets_readback(value, set_point_pv="TEMP:SP", readback_pv="TEMP:SP:RBV") + + def test_WHEN_heater_range_is_set_THEN_readback_updates(self): + for value in HEATER_RANGE_TEST_VALUES: + self.ca.assert_setting_setpoint_sets_readback(value, "HEATER:RANGE") From 635af941ba5948161eae565eec98789fb11b85b3 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 19 Dec 2017 16:40:22 +0000 Subject: [PATCH 13/72] Get heater power --- lewis_emulators/triton/device.py | 8 ++- .../triton/interfaces/stream_interface.py | 64 ++++++++++++++----- 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 0486322..4d1815e 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -4,8 +4,9 @@ SUBSYSTEM_NAMES = { - "mixing chamber": "mix_chamber_name", - } + "mixing chamber": "mix_chamber_name", + "heater": "H5" +} class SimulatedTriton(StateMachineDevice): @@ -17,6 +18,9 @@ def _initialize_data(self): self.temperature_setpoint = 0 self.heater_range = 0 + self.heater_power = 1 + self.heater_power_units = "mA" + self.p = 0 self.i = 0 self.d = 0 diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index db94a1b..c2a93c5 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -1,23 +1,32 @@ from lewis.adapters.stream import StreamInterface, Cmd +from lewis.core.logging import has_log from lewis_emulators.utils.command_builder import CmdBuilder from lewis_emulators.triton.device import SUBSYSTEM_NAMES +@has_log class TritonStreamInterface(StreamInterface): # Commands that we expect via serial during normal operation commands = { - CmdBuilder("get_mc_uid").escape("READ:SYS:DR:CHAN:MC").build(), + CmdBuilder("get_mc_uid") + .escape("READ:SYS:DR:CHAN:MC").build(), # PID setpoints - CmdBuilder("set_p").escape("SET:DEV:{}:TEMP:LOOP:P:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), - CmdBuilder("set_i").escape("SET:DEV:{}:TEMP:LOOP:I:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), - CmdBuilder("set_d").escape("SET:DEV:{}:TEMP:LOOP:D:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + CmdBuilder("set_p") + .escape("SET:DEV:{}:TEMP:LOOP:P:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + CmdBuilder("set_i") + .escape("SET:DEV:{}:TEMP:LOOP:I:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + CmdBuilder("set_d") + .escape("SET:DEV:{}:TEMP:LOOP:D:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), # PID readbacks - CmdBuilder("get_p").escape("READ:DEV:{}:TEMP:LOOP:P".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), - CmdBuilder("get_i").escape("READ:DEV:{}:TEMP:LOOP:I".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), - CmdBuilder("get_d").escape("READ:DEV:{}:TEMP:LOOP:D".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + CmdBuilder("get_p") + .escape("READ:DEV:{}:TEMP:LOOP:P".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + CmdBuilder("get_i") + .escape("READ:DEV:{}:TEMP:LOOP:I".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + CmdBuilder("get_d") + .escape("READ:DEV:{}:TEMP:LOOP:D".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), # Setpoint temperature CmdBuilder("set_temperature_setpoint") @@ -30,13 +39,27 @@ class TritonStreamInterface(StreamInterface): .escape("SET:DEV:{}:TEMP:LOOP:RANGE:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), CmdBuilder("get_heater_range") .escape("READ:DEV:{}:TEMP:LOOP:RANGE".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + + # Heater type + CmdBuilder("get_heater_type") + .escape("READ:DEV:{}:TEMP:LOOP:HTR".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + + # Get heater power + CmdBuilder("get_heater_power") + .escape("READ:DEV:{}:HTR:SIG:POWR".format(SUBSYSTEM_NAMES["heater"])).build(), } in_terminator = "\r\n" out_terminator = "\r\n" + def handle_error(self, request, error): + err = "Request: {}, error: {}".format(request, error) + print(err) + return err + def get_mc_uid(self): - return "STAT:SYS:DR:CHAN:MC:{}".format(SUBSYSTEM_NAMES["mixing chamber"]) + return "STAT:SYS:DR:CHAN:MC:{}" \ + .format(SUBSYSTEM_NAMES["mixing chamber"]) def set_p(self, value): self.device.set_p(value) @@ -51,24 +74,35 @@ def set_d(self, value): return "ok" def get_p(self): - return "STAT:DEV:{}:TEMP:LOOP:P:{}".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_p()) + return "STAT:DEV:{}:TEMP:LOOP:P:{}" \ + .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_p()) def get_i(self): - return "STAT:DEV:{}:TEMP:LOOP:I:{}".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_i()) + return "STAT:DEV:{}:TEMP:LOOP:I:{}" \ + .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_i()) def get_d(self): - return "STAT:DEV:{}:TEMP:LOOP:D:{}".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_d()) + return "STAT:DEV:{}:TEMP:LOOP:D:{}" \ + .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_d()) def set_temperature_setpoint(self, value): self.device.set_temperature_setpoint(value) def get_temperature_setpoint(self): - return "STAT:DEV:{}:TEMP:LOOP:TSET:{}K".format(SUBSYSTEM_NAMES["mixing chamber"], - self.device.get_temperature_setpoint()) + return "STAT:DEV:{}:TEMP:LOOP:TSET:{}K" \ + .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_temperature_setpoint()) def set_heater_range(self, value): self.device.set_heater_range(value) def get_heater_range(self): - return "STAT:DEV:{}:TEMP:LOOP:RANGE:{}".format(SUBSYSTEM_NAMES["mixing chamber"], - self.device.get_heater_range()) + return "STAT:DEV:{}:TEMP:LOOP:RANGE:{}" \ + .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_heater_range()) + + def get_heater_type(self): + return "STAT:DEV:{}:TEMP:LOOP:HTR:{}" \ + .format(SUBSYSTEM_NAMES["mixing chamber"], SUBSYSTEM_NAMES["heater"]) + + def get_heater_power(self): + return "STAT:DEV:{}:HTR:SIG:POWR:{}{}"\ + .format(SUBSYSTEM_NAMES["heater"], self.device.heater_power, self.device.heater_power_units) From cd9ede692ca810f456313a1ab0f573859821b048 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 19 Dec 2017 16:40:39 +0000 Subject: [PATCH 14/72] Heater power tests --- tests/triton.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/triton.py b/tests/triton.py index 0c0ead8..acd65bb 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -46,3 +46,15 @@ def test_WHEN_temperature_setpoint_is_set_THEN_readback_updates(self): def test_WHEN_heater_range_is_set_THEN_readback_updates(self): for value in HEATER_RANGE_TEST_VALUES: self.ca.assert_setting_setpoint_sets_readback(value, "HEATER:RANGE") + + def test_heater_power(self): + self._lewis.backdoor_set_on_device("heater_power_units", "mA") + for value in HEATER_RANGE_TEST_VALUES: + self._lewis.backdoor_set_on_device("heater_power", value) + self.ca.assert_that_pv_is("HEATER:POWER", value) + self.ca.assert_that_pv_is("HEATER:POWER.EGU", "mA") + + def test_heater_power_units(self): + for unit in ["A", "mA", "uA", "nA", "pA"]: + self._lewis.backdoor_set_on_device("heater_power_units", unit) + self.ca.assert_that_pv_is("HEATER:POWER.EGU", unit) From 40d04147fd1c0dac261933b4b942c4a3c68a5a53 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 19 Dec 2017 17:22:32 +0000 Subject: [PATCH 15/72] Add commands --- lewis_emulators/triton/device.py | 2 ++ lewis_emulators/triton/interfaces/stream_interface.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 4d1815e..619d1d1 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -25,6 +25,8 @@ def _initialize_data(self): self.i = 0 self.d = 0 + self.closed_loop = False + def _get_state_handlers(self): return { 'default': DefaultState(), diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index c2a93c5..d2aa48f 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -47,6 +47,10 @@ class TritonStreamInterface(StreamInterface): # Get heater power CmdBuilder("get_heater_power") .escape("READ:DEV:{}:HTR:SIG:POWR".format(SUBSYSTEM_NAMES["heater"])).build(), + + # Loop mode + CmdBuilder("get_closed_loop_mode") + .escape("READ:DEV:{}:TEMP:LOOP:MODE".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), } in_terminator = "\r\n" @@ -87,6 +91,7 @@ def get_d(self): def set_temperature_setpoint(self, value): self.device.set_temperature_setpoint(value) + return "ok" def get_temperature_setpoint(self): return "STAT:DEV:{}:TEMP:LOOP:TSET:{}K" \ @@ -94,6 +99,7 @@ def get_temperature_setpoint(self): def set_heater_range(self, value): self.device.set_heater_range(value) + return "ok" def get_heater_range(self): return "STAT:DEV:{}:TEMP:LOOP:RANGE:{}" \ @@ -106,3 +112,7 @@ def get_heater_type(self): def get_heater_power(self): return "STAT:DEV:{}:HTR:SIG:POWR:{}{}"\ .format(SUBSYSTEM_NAMES["heater"], self.device.heater_power, self.device.heater_power_units) + + def get_closed_loop_mode(self): + return "STAT:DEV:{}:TEMP:LOOP:MODE:{}"\ + .format(SUBSYSTEM_NAMES["mixing chamber"], "ON" if self.device.closed_loop else "OFF") From 27979f876236a217adee49b1f853f4aedf5d2c7c Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 19 Dec 2017 17:22:48 +0000 Subject: [PATCH 16/72] Add tests --- tests/triton.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/triton.py b/tests/triton.py index acd65bb..96bf5f4 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -23,7 +23,7 @@ def setUp(self): def test_WHEN_device_is_started_THEN_it_is_not_disabled(self): self.ca.assert_that_pv_is("DISABLE", "COMMS ENABLED") - @skipIf(IOCRegister.uses_rec_sim, "Not implemented in recsim") + @skipIf(IOCRegister.uses_rec_sim, "Not implemented in recsim.") def test_WHEN_device_is_started_THEN_can_get_mixing_chamber_uid(self): self.ca.assert_that_pv_is("MC:UID", "mix_chamber_name") @@ -47,6 +47,7 @@ def test_WHEN_heater_range_is_set_THEN_readback_updates(self): for value in HEATER_RANGE_TEST_VALUES: self.ca.assert_setting_setpoint_sets_readback(value, "HEATER:RANGE") + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_heater_power(self): self._lewis.backdoor_set_on_device("heater_power_units", "mA") for value in HEATER_RANGE_TEST_VALUES: @@ -54,7 +55,14 @@ def test_heater_power(self): self.ca.assert_that_pv_is("HEATER:POWER", value) self.ca.assert_that_pv_is("HEATER:POWER.EGU", "mA") + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_heater_power_units(self): for unit in ["A", "mA", "uA", "nA", "pA"]: self._lewis.backdoor_set_on_device("heater_power_units", unit) self.ca.assert_that_pv_is("HEATER:POWER.EGU", unit) + + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + def test_closed_loop(self): + for value in [False, True, False]: # Need to check both transitions work properly + self._lewis.backdoor_set_on_device("closed_loop", value) + self.ca.assert_that_pv_is("CLOSEDLOOP", "YES" if value else "NO") From dc26bcb3141f996a6260c70c6e0c64249f1bde44 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 09:54:40 +0000 Subject: [PATCH 17/72] Add emulation for valve states --- lewis_emulators/triton/device.py | 17 +++++++++++ .../triton/interfaces/stream_interface.py | 29 +++++++++++++++---- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 619d1d1..5cf9277 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -9,6 +9,15 @@ } +class ValveStates(object): + """ + Enum representing the possible states of a valve. + """ + OPEN = 0 + CLOSED = 1 + NOT_FOUND = 2 + + class SimulatedTriton(StateMachineDevice): def _initialize_data(self): @@ -27,6 +36,11 @@ def _initialize_data(self): self.closed_loop = False + self.valves = [ValveStates.CLOSED] * 10 + + def set_valve_state_backdoor(self, valve, newstate): + self.valves[int(valve) - 1] = int(newstate) + def _get_state_handlers(self): return { 'default': DefaultState(), @@ -68,3 +82,6 @@ def get_heater_range(self): def set_heater_range(self, value): self.heater_range = value + + def get_valve_state(self, valve): + return self.valves[valve-1] diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index d2aa48f..7860751 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -2,6 +2,7 @@ from lewis.core.logging import has_log from lewis_emulators.utils.command_builder import CmdBuilder from lewis_emulators.triton.device import SUBSYSTEM_NAMES +from lewis_emulators.triton.device import ValveStates @has_log @@ -51,6 +52,10 @@ class TritonStreamInterface(StreamInterface): # Loop mode CmdBuilder("get_closed_loop_mode") .escape("READ:DEV:{}:TEMP:LOOP:MODE".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + + # Valve state + CmdBuilder("get_valve_state") + .escape("READ:DEV:V").int().escape(":VALV:SIG:STATE").build(), } in_terminator = "\r\n" @@ -59,6 +64,7 @@ class TritonStreamInterface(StreamInterface): def handle_error(self, request, error): err = "Request: {}, error: {}".format(request, error) print(err) + self.log.error(err) return err def get_mc_uid(self): @@ -66,15 +72,15 @@ def get_mc_uid(self): .format(SUBSYSTEM_NAMES["mixing chamber"]) def set_p(self, value): - self.device.set_p(value) + self.device.set_p(float(value)) return "ok" def set_i(self, value): - self.device.set_i(value) + self.device.set_i(float(value)) return "ok" def set_d(self, value): - self.device.set_d(value) + self.device.set_d(float(value)) return "ok" def get_p(self): @@ -90,7 +96,7 @@ def get_d(self): .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_d()) def set_temperature_setpoint(self, value): - self.device.set_temperature_setpoint(value) + self.device.set_temperature_setpoint(float(value)) return "ok" def get_temperature_setpoint(self): @@ -98,7 +104,7 @@ def get_temperature_setpoint(self): .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_temperature_setpoint()) def set_heater_range(self, value): - self.device.set_heater_range(value) + self.device.set_heater_range(float(value)) return "ok" def get_heater_range(self): @@ -116,3 +122,16 @@ def get_heater_power(self): def get_closed_loop_mode(self): return "STAT:DEV:{}:TEMP:LOOP:MODE:{}"\ .format(SUBSYSTEM_NAMES["mixing chamber"], "ON" if self.device.closed_loop else "OFF") + + def get_valve_state(self, valve): + + state = self.device.get_valve_state(int(valve)) + + if state == ValveStates.CLOSED: + response = "CLOSE" + elif state == ValveStates.OPEN: + response = "OPEN" + else: + response = "NOT_FOUND" + + return "STAT:DEV:V{}:VALV:SIG:STATE:{}".format(valve, response) From b0b835b9706d7cf10bddaff8dd29373f1efa713c Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 09:54:58 +0000 Subject: [PATCH 18/72] Add valve state tests --- tests/triton.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/triton.py b/tests/triton.py index 96bf5f4..d2798bc 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -10,6 +10,8 @@ PID_TEST_VALUES = 0, 10**-5, 123.45, 10**5 TEMPERATURE_TEST_VALUES = 0, 10**-5, 5.4321, 1000 HEATER_RANGE_TEST_VALUES = 0.001, 0.316, 1000 +HEATER_POWER_UNITS = ["A", "mA", "uA", "nA", "pA"] +VALVE_STATES = ["OPEN", "CLOSED", "NOT_FOUND"] class TritonTests(unittest.TestCase): @@ -56,13 +58,20 @@ def test_heater_power(self): self.ca.assert_that_pv_is("HEATER:POWER.EGU", "mA") @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") - def test_heater_power_units(self): - for unit in ["A", "mA", "uA", "nA", "pA"]: + def test_WHEN_heater_units_are_set_via_backdoor_THEN_egu_field_on_heater_power_updates_with_the_unit_just_set(self): + for unit in HEATER_POWER_UNITS: self._lewis.backdoor_set_on_device("heater_power_units", unit) self.ca.assert_that_pv_is("HEATER:POWER.EGU", unit) @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") - def test_closed_loop(self): + def test_WHEN_closed_loop_mode_is_set_via_backdoor_THEN_the_closed_loop_pv_updates_with_value_just_set(self): for value in [False, True, False]: # Need to check both transitions work properly self._lewis.backdoor_set_on_device("closed_loop", value) self.ca.assert_that_pv_is("CLOSEDLOOP", "YES" if value else "NO") + + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + def test_WHEN_valve_state_is_set_via_backdoor_THEN_valve_state_pvs_update_with_value_just_set(self): + for valve in range(1, 11): + for valve_state_index, valve_state in enumerate(VALVE_STATES): + self._lewis.backdoor_command(["device", "set_valve_state_backdoor", str(valve), str(valve_state_index)]) + self.ca.assert_that_pv_is("VALVES:V{}:STATE".format(valve), valve_state) From d2c7b51d6f97163c3e19206f8015ca058296d460 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 11:31:57 +0000 Subject: [PATCH 19/72] Channel enablement --- lewis_emulators/triton/device.py | 8 +++++++ .../triton/interfaces/stream_interface.py | 24 +++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 5cf9277..7e9ea46 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -38,6 +38,8 @@ def _initialize_data(self): self.valves = [ValveStates.CLOSED] * 10 + self.channels_enabled = [True] * 6 + def set_valve_state_backdoor(self, valve, newstate): self.valves[int(valve) - 1] = int(newstate) @@ -85,3 +87,9 @@ def set_heater_range(self, value): def get_valve_state(self, valve): return self.valves[valve-1] + + def is_channel_enabled(self, chan): + return self.channels_enabled[chan-1] + + def set_channel_enabled(self, chan, newstate): + self.channels_enabled[chan-1] = newstate diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 7860751..b3a3ff2 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -56,13 +56,21 @@ class TritonStreamInterface(StreamInterface): # Valve state CmdBuilder("get_valve_state") .escape("READ:DEV:V").int().escape(":VALV:SIG:STATE").build(), + + # Channel enablement + CmdBuilder("get_channel_enabled") + .escape("READ:DEV:T").int().escape(":TEMP:MEAS:ENAB").build(), + CmdBuilder("set_channel_enabled") + .escape("SET:DEV:T").int().escape(":TEMP:MEAS:ENAB:").any().build(), } in_terminator = "\r\n" out_terminator = "\r\n" def handle_error(self, request, error): - err = "Request: {}, error: {}".format(request, error) + err = "Request: {}, error: {}. \n\nAvailable commands: {}"\ + .format(request, error, [c.pattern for c in self.commands]) + print(err) self.log.error(err) return err @@ -131,7 +139,19 @@ def get_valve_state(self, valve): response = "CLOSE" elif state == ValveStates.OPEN: response = "OPEN" - else: + elif state == ValveStates.NOT_FOUND: response = "NOT_FOUND" + else: + raise ValueError("Invalid valve state: {}".format(state)) return "STAT:DEV:V{}:VALV:SIG:STATE:{}".format(valve, response) + + def get_channel_enabled(self, channel): + return "STAT:DEV:T{}:TEMP:MEAS:ENAB:{}"\ + .format(channel, "ON" if self.device.is_channel_enabled(int(channel)) else "OFF") + + def set_channel_enabled(self, channel, newstate): + if newstate not in ["ON", "OFF"]: + raise ValueError("New state '{}' not valid.".format(newstate)) + self.device.set_channel_enabled(int(channel), str(newstate) == "ON") + return "ok" From e7bdc1c298641b006ad3439f780bcc8ee56dfa55 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 11:32:14 +0000 Subject: [PATCH 20/72] =?UTF-8?q?Test=20for=20channel=20enablement=C2=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/triton.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/triton.py b/tests/triton.py index d2798bc..9a57f33 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -75,3 +75,9 @@ def test_WHEN_valve_state_is_set_via_backdoor_THEN_valve_state_pvs_update_with_v for valve_state_index, valve_state in enumerate(VALVE_STATES): self._lewis.backdoor_command(["device", "set_valve_state_backdoor", str(valve), str(valve_state_index)]) self.ca.assert_that_pv_is("VALVES:V{}:STATE".format(valve), valve_state) + + def test_channels(self): + for chan in range(1, 7): + for enabled in [False, True, False]: # Need to check both transitions work properly + self.ca.assert_setting_setpoint_sets_readback( + "ON" if enabled else "OFF", "CHANNELS:T{}:STATE".format(chan)) From e0e8b9b525d6fd530e083d0f97eb08c1ed0c0f44 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 13:45:48 +0000 Subject: [PATCH 21/72] Add device status --- lewis_emulators/triton/device.py | 5 +++++ lewis_emulators/triton/interfaces/stream_interface.py | 11 ++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 7e9ea46..1eb0e25 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -40,6 +40,8 @@ def _initialize_data(self): self.channels_enabled = [True] * 6 + self.status = "This is a device status message." + def set_valve_state_backdoor(self, valve, newstate): self.valves[int(valve) - 1] = int(newstate) @@ -93,3 +95,6 @@ def is_channel_enabled(self, chan): def set_channel_enabled(self, chan, newstate): self.channels_enabled[chan-1] = newstate + + def get_status(self): + return self.status diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index b3a3ff2..16daf75 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -62,15 +62,17 @@ class TritonStreamInterface(StreamInterface): .escape("READ:DEV:T").int().escape(":TEMP:MEAS:ENAB").build(), CmdBuilder("set_channel_enabled") .escape("SET:DEV:T").int().escape(":TEMP:MEAS:ENAB:").any().build(), + + # Status + CmdBuilder("get_status") + .escape("READ:SYS:DR:STATUS").build(), } in_terminator = "\r\n" out_terminator = "\r\n" def handle_error(self, request, error): - err = "Request: {}, error: {}. \n\nAvailable commands: {}"\ - .format(request, error, [c.pattern for c in self.commands]) - + err = "Request: {}, error: {}." print(err) self.log.error(err) return err @@ -155,3 +157,6 @@ def set_channel_enabled(self, channel, newstate): raise ValueError("New state '{}' not valid.".format(newstate)) self.device.set_channel_enabled(int(channel), str(newstate) == "ON") return "ok" + + def get_status(self): + return "STAT:SYS:DR:STATUS:{}".format(self.device.get_status()) From 6d2fccbae67f1dc394999133e93388f86996acae Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 13:46:05 +0000 Subject: [PATCH 22/72] Add device status --- tests/triton.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/triton.py b/tests/triton.py index 9a57f33..0a59124 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -81,3 +81,34 @@ def test_channels(self): for enabled in [False, True, False]: # Need to check both transitions work properly self.ca.assert_setting_setpoint_sets_readback( "ON" if enabled else "OFF", "CHANNELS:T{}:STATE".format(chan)) + + def test_WHEN_a_short_status_is_set_on_device_THEN_displayed_status_is_identical(self): + # Status message that could be contained in an EPICS string type + SHORT_STATUS = "Device status" + assert 0 < len(SHORT_STATUS) < 40 + + # Status message that device is likely to return - longer than EPICS string type but reasonable for a protocol + MEDIUM_STATUS = "This is a device status that contains a bit more information" + assert 40 < len(MEDIUM_STATUS) < 256 + + # Short and medium statuses should be displayed in full. + for status in [SHORT_STATUS, MEDIUM_STATUS]: + self._lewis.backdoor_set_on_device("status", status) + self.ca.assert_that_pv_is("STATUS", status) + + def test_WHEN_long_status_is_set_on_device_THEN_displayed_status_truncated_but_displays_at_least_500_chars(self): + + # Somewhat arbitrary, but decide on a minimum number of characters that should be displayed in a + # status message to the user if the status message is very long. This seems to be a reasonable + # number given the messages expected, but the manual does not provide an exhaustive list. + minimum_characters_in_pv = 500 + + # Very long status message, used to check that very long messages can be handled gracefully + LONG_STATUS = "This device status is quite long:" + " (here is a load of information)" * 50 + + assert minimum_characters_in_pv < len(LONG_STATUS) + + # Allow truncation for long status, but it should still display as many characters as possible + self._lewis.backdoor_set_on_device("status", LONG_STATUS) + self.ca.assert_pv_value_causes_func_to_return_true( + "STATUS", lambda val: LONG_STATUS.startswith(val) and len(val) >= minimum_characters_in_pv) From 6e3a300859a61dc50e06b7e3cd12a01951c30601 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 13:59:00 +0000 Subject: [PATCH 23/72] Add automation --- lewis_emulators/triton/device.py | 4 ++++ lewis_emulators/triton/interfaces/stream_interface.py | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 1eb0e25..cdfad98 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -41,6 +41,7 @@ def _initialize_data(self): self.channels_enabled = [True] * 6 self.status = "This is a device status message." + self.automation = "This is the automation status" def set_valve_state_backdoor(self, valve, newstate): self.valves[int(valve) - 1] = int(newstate) @@ -98,3 +99,6 @@ def set_channel_enabled(self, chan, newstate): def get_status(self): return self.status + + def get_automation(self): + return self.automation diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 16daf75..632246f 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -66,6 +66,10 @@ class TritonStreamInterface(StreamInterface): # Status CmdBuilder("get_status") .escape("READ:SYS:DR:STATUS").build(), + + # Automation + CmdBuilder("get_automation") + .escape("READ:SYS:DR:ACTN").build(), } in_terminator = "\r\n" @@ -160,3 +164,6 @@ def set_channel_enabled(self, channel, newstate): def get_status(self): return "STAT:SYS:DR:STATUS:{}".format(self.device.get_status()) + + def get_automation(self): + return "STAT:SYS:DR:ACTN:{}".format(self.device.get_automation()) From e0886fa25e963751deae6e9e4181f9e1347b5ac2 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 13:59:15 +0000 Subject: [PATCH 24/72] Add automation --- tests/triton.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/triton.py b/tests/triton.py index 0a59124..b80e18d 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -112,3 +112,13 @@ def test_WHEN_long_status_is_set_on_device_THEN_displayed_status_truncated_but_d self._lewis.backdoor_set_on_device("status", LONG_STATUS) self.ca.assert_pv_value_causes_func_to_return_true( "STATUS", lambda val: LONG_STATUS.startswith(val) and len(val) >= minimum_characters_in_pv) + + def test_WHEN_automation_is_set_on_device_THEN_displayed_automation_is_identical(self): + automations = [ + "Warming up to 200K", + "Cooling down to 1K", + ] + + for automation in automations: + self._lewis.backdoor_set_on_device("automation", automation) + self.ca.assert_that_pv_is("AUTOMATION", automation) From 08ea169a994bf32d5baff2b3f888f1656a593cf2 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 14:27:45 +0000 Subject: [PATCH 25/72] STIL/MC temperatures --- lewis_emulators/triton/device.py | 10 ++++++++++ .../triton/interfaces/stream_interface.py | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index cdfad98..059d1a4 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -5,6 +5,7 @@ SUBSYSTEM_NAMES = { "mixing chamber": "mix_chamber_name", + "stil": "T1", "heater": "H5" } @@ -43,6 +44,9 @@ def _initialize_data(self): self.status = "This is a device status message." self.automation = "This is the automation status" + self.stil_temp = 0 + self.mc_temp = 0 + def set_valve_state_backdoor(self, valve, newstate): self.valves[int(valve) - 1] = int(newstate) @@ -102,3 +106,9 @@ def get_status(self): def get_automation(self): return self.automation + + def get_stil_temp(self): + return self.stil_temp + + def get_mc_temp(self): + return self.mc_temp diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 632246f..8140c45 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -10,8 +10,11 @@ class TritonStreamInterface(StreamInterface): # Commands that we expect via serial during normal operation commands = { + # UIDs CmdBuilder("get_mc_uid") .escape("READ:SYS:DR:CHAN:MC").build(), + CmdBuilder("get_stil_uid") + .escape("READ:SYS:DR:CHAN:STIL").build(), # PID setpoints CmdBuilder("set_p") @@ -35,6 +38,12 @@ class TritonStreamInterface(StreamInterface): CmdBuilder("get_temperature_setpoint") .escape("READ:DEV:{}:TEMP:LOOP:TSET".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + # Temperature + CmdBuilder("get_stil_temp") + .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["stil"])).build(), + CmdBuilder("get_mc_temp") + .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + # Heater range CmdBuilder("set_heater_range") .escape("SET:DEV:{}:TEMP:LOOP:RANGE:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), @@ -85,6 +94,10 @@ def get_mc_uid(self): return "STAT:SYS:DR:CHAN:MC:{}" \ .format(SUBSYSTEM_NAMES["mixing chamber"]) + def get_stil_uid(self): + return "STAT:SYS:DR:CHAN:STIL:{}" \ + .format(SUBSYSTEM_NAMES["stil"]) + def set_p(self, value): self.device.set_p(float(value)) return "ok" @@ -167,3 +180,9 @@ def get_status(self): def get_automation(self): return "STAT:SYS:DR:ACTN:{}".format(self.device.get_automation()) + + def get_stil_temp(self): + return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["stil"], self.device.get_stil_temp()) + + def get_mc_temp(self): + return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_mc_temp()) From cd4bd4587d709a9a4baf96ab9eda1f25ffb2402f Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 14:28:04 +0000 Subject: [PATCH 26/72] STIL/MC temperatures --- tests/triton.py | 211 +++++++++++++++++++++++++----------------------- 1 file changed, 111 insertions(+), 100 deletions(-) diff --git a/tests/triton.py b/tests/triton.py index b80e18d..f55ccf9 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -22,103 +22,114 @@ def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc("triton") self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX) - def test_WHEN_device_is_started_THEN_it_is_not_disabled(self): - self.ca.assert_that_pv_is("DISABLE", "COMMS ENABLED") - - @skipIf(IOCRegister.uses_rec_sim, "Not implemented in recsim.") - def test_WHEN_device_is_started_THEN_can_get_mixing_chamber_uid(self): - self.ca.assert_that_pv_is("MC:UID", "mix_chamber_name") - - def test_WHEN_P_setpoint_is_set_THEN_readback_updates(self): - for value in PID_TEST_VALUES: - self.ca.assert_setting_setpoint_sets_readback(value, "P") - - def test_WHEN_I_setpoint_is_set_THEN_readback_updates(self): - for value in PID_TEST_VALUES: - self.ca.assert_setting_setpoint_sets_readback(value, "I") - - def test_WHEN_D_setpoint_is_set_THEN_readback_updates(self): - for value in PID_TEST_VALUES: - self.ca.assert_setting_setpoint_sets_readback(value, "D") - - def test_WHEN_temperature_setpoint_is_set_THEN_readback_updates(self): - for value in TEMPERATURE_TEST_VALUES: - self.ca.assert_setting_setpoint_sets_readback(value, set_point_pv="TEMP:SP", readback_pv="TEMP:SP:RBV") - - def test_WHEN_heater_range_is_set_THEN_readback_updates(self): - for value in HEATER_RANGE_TEST_VALUES: - self.ca.assert_setting_setpoint_sets_readback(value, "HEATER:RANGE") - - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") - def test_heater_power(self): - self._lewis.backdoor_set_on_device("heater_power_units", "mA") - for value in HEATER_RANGE_TEST_VALUES: - self._lewis.backdoor_set_on_device("heater_power", value) - self.ca.assert_that_pv_is("HEATER:POWER", value) - self.ca.assert_that_pv_is("HEATER:POWER.EGU", "mA") - - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") - def test_WHEN_heater_units_are_set_via_backdoor_THEN_egu_field_on_heater_power_updates_with_the_unit_just_set(self): - for unit in HEATER_POWER_UNITS: - self._lewis.backdoor_set_on_device("heater_power_units", unit) - self.ca.assert_that_pv_is("HEATER:POWER.EGU", unit) - - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") - def test_WHEN_closed_loop_mode_is_set_via_backdoor_THEN_the_closed_loop_pv_updates_with_value_just_set(self): - for value in [False, True, False]: # Need to check both transitions work properly - self._lewis.backdoor_set_on_device("closed_loop", value) - self.ca.assert_that_pv_is("CLOSEDLOOP", "YES" if value else "NO") - - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") - def test_WHEN_valve_state_is_set_via_backdoor_THEN_valve_state_pvs_update_with_value_just_set(self): - for valve in range(1, 11): - for valve_state_index, valve_state in enumerate(VALVE_STATES): - self._lewis.backdoor_command(["device", "set_valve_state_backdoor", str(valve), str(valve_state_index)]) - self.ca.assert_that_pv_is("VALVES:V{}:STATE".format(valve), valve_state) - - def test_channels(self): - for chan in range(1, 7): - for enabled in [False, True, False]: # Need to check both transitions work properly - self.ca.assert_setting_setpoint_sets_readback( - "ON" if enabled else "OFF", "CHANNELS:T{}:STATE".format(chan)) - - def test_WHEN_a_short_status_is_set_on_device_THEN_displayed_status_is_identical(self): - # Status message that could be contained in an EPICS string type - SHORT_STATUS = "Device status" - assert 0 < len(SHORT_STATUS) < 40 - - # Status message that device is likely to return - longer than EPICS string type but reasonable for a protocol - MEDIUM_STATUS = "This is a device status that contains a bit more information" - assert 40 < len(MEDIUM_STATUS) < 256 - - # Short and medium statuses should be displayed in full. - for status in [SHORT_STATUS, MEDIUM_STATUS]: - self._lewis.backdoor_set_on_device("status", status) - self.ca.assert_that_pv_is("STATUS", status) - - def test_WHEN_long_status_is_set_on_device_THEN_displayed_status_truncated_but_displays_at_least_500_chars(self): - - # Somewhat arbitrary, but decide on a minimum number of characters that should be displayed in a - # status message to the user if the status message is very long. This seems to be a reasonable - # number given the messages expected, but the manual does not provide an exhaustive list. - minimum_characters_in_pv = 500 - - # Very long status message, used to check that very long messages can be handled gracefully - LONG_STATUS = "This device status is quite long:" + " (here is a load of information)" * 50 - - assert minimum_characters_in_pv < len(LONG_STATUS) - - # Allow truncation for long status, but it should still display as many characters as possible - self._lewis.backdoor_set_on_device("status", LONG_STATUS) - self.ca.assert_pv_value_causes_func_to_return_true( - "STATUS", lambda val: LONG_STATUS.startswith(val) and len(val) >= minimum_characters_in_pv) - - def test_WHEN_automation_is_set_on_device_THEN_displayed_automation_is_identical(self): - automations = [ - "Warming up to 200K", - "Cooling down to 1K", - ] - - for automation in automations: - self._lewis.backdoor_set_on_device("automation", automation) - self.ca.assert_that_pv_is("AUTOMATION", automation) + # def test_WHEN_device_is_started_THEN_it_is_not_disabled(self): + # self.ca.assert_that_pv_is("DISABLE", "COMMS ENABLED") + # + # @skipIf(IOCRegister.uses_rec_sim, "Not implemented in recsim.") + # def test_WHEN_device_is_started_THEN_can_get_mixing_chamber_uid(self): + # self.ca.assert_that_pv_is("MC:UID", "mix_chamber_name") + # + # def test_WHEN_P_setpoint_is_set_THEN_readback_updates(self): + # for value in PID_TEST_VALUES: + # self.ca.assert_setting_setpoint_sets_readback(value, "P") + # + # def test_WHEN_I_setpoint_is_set_THEN_readback_updates(self): + # for value in PID_TEST_VALUES: + # self.ca.assert_setting_setpoint_sets_readback(value, "I") + # + # def test_WHEN_D_setpoint_is_set_THEN_readback_updates(self): + # for value in PID_TEST_VALUES: + # self.ca.assert_setting_setpoint_sets_readback(value, "D") + # + # def test_WHEN_temperature_setpoint_is_set_THEN_readback_updates(self): + # for value in TEMPERATURE_TEST_VALUES: + # self.ca.assert_setting_setpoint_sets_readback(value, set_point_pv="TEMP:SP", readback_pv="TEMP:SP:RBV") + # + # def test_WHEN_heater_range_is_set_THEN_readback_updates(self): + # for value in HEATER_RANGE_TEST_VALUES: + # self.ca.assert_setting_setpoint_sets_readback(value, "HEATER:RANGE") + # + # @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + # def test_heater_power(self): + # self._lewis.backdoor_set_on_device("heater_power_units", "mA") + # for value in HEATER_RANGE_TEST_VALUES: + # self._lewis.backdoor_set_on_device("heater_power", value) + # self.ca.assert_that_pv_is("HEATER:POWER", value) + # self.ca.assert_that_pv_is("HEATER:POWER.EGU", "mA") + # + # @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + # def test_WHEN_heater_units_are_set_via_backdoor_THEN_egu_field_on_heater_power_updates_with_the_unit_just_set(self): + # for unit in HEATER_POWER_UNITS: + # self._lewis.backdoor_set_on_device("heater_power_units", unit) + # self.ca.assert_that_pv_is("HEATER:POWER.EGU", unit) + # + # @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + # def test_WHEN_closed_loop_mode_is_set_via_backdoor_THEN_the_closed_loop_pv_updates_with_value_just_set(self): + # for value in [False, True, False]: # Need to check both transitions work properly + # self._lewis.backdoor_set_on_device("closed_loop", value) + # self.ca.assert_that_pv_is("CLOSEDLOOP", "YES" if value else "NO") + # + # @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + # def test_WHEN_valve_state_is_set_via_backdoor_THEN_valve_state_pvs_update_with_value_just_set(self): + # for valve in range(1, 11): + # for valve_state_index, valve_state in enumerate(VALVE_STATES): + # self._lewis.backdoor_command(["device", "set_valve_state_backdoor", str(valve), str(valve_state_index)]) + # self.ca.assert_that_pv_is("VALVES:V{}:STATE".format(valve), valve_state) + # + # def test_channels(self): + # for chan in range(1, 7): + # for enabled in [False, True, False]: # Need to check both transitions work properly + # self.ca.assert_setting_setpoint_sets_readback( + # "ON" if enabled else "OFF", "CHANNELS:T{}:STATE".format(chan)) + # + # def test_WHEN_a_short_status_is_set_on_device_THEN_displayed_status_is_identical(self): + # # Status message that could be contained in an EPICS string type + # SHORT_STATUS = "Device status" + # assert 0 < len(SHORT_STATUS) < 40 + # + # # Status message that device is likely to return - longer than EPICS string type but reasonable for a protocol + # MEDIUM_STATUS = "This is a device status that contains a bit more information" + # assert 40 < len(MEDIUM_STATUS) < 256 + # + # # Short and medium statuses should be displayed in full. + # for status in [SHORT_STATUS, MEDIUM_STATUS]: + # self._lewis.backdoor_set_on_device("status", status) + # self.ca.assert_that_pv_is("STATUS", status) + # + # def test_WHEN_long_status_is_set_on_device_THEN_displayed_status_truncated_but_displays_at_least_500_chars(self): + # + # # Somewhat arbitrary, but decide on a minimum number of characters that should be displayed in a + # # status message to the user if the status message is very long. This seems to be a reasonable + # # number given the messages expected, but the manual does not provide an exhaustive list. + # minimum_characters_in_pv = 500 + # + # # Very long status message, used to check that very long messages can be handled gracefully + # LONG_STATUS = "This device status is quite long:" + " (here is a load of information)" * 50 + # + # assert minimum_characters_in_pv < len(LONG_STATUS) + # + # # Allow truncation for long status, but it should still display as many characters as possible + # self._lewis.backdoor_set_on_device("status", LONG_STATUS) + # self.ca.assert_pv_value_causes_func_to_return_true( + # "STATUS", lambda val: LONG_STATUS.startswith(val) and len(val) >= minimum_characters_in_pv) + # + # def test_WHEN_automation_is_set_on_device_THEN_displayed_automation_is_identical(self): + # automations = [ + # "Warming up to 200K", + # "Cooling down to 1K", + # ] + # + # for automation in automations: + # self._lewis.backdoor_set_on_device("automation", automation) + # self.ca.assert_that_pv_is("AUTOMATION", automation) + + def test_WHEN_stil_temp_is_set_via_backdoor_THEN_pv_updates(self): + for temp in TEMPERATURE_TEST_VALUES: + self._lewis.backdoor_set_on_device("stil_temp", temp) + self.ca.assert_that_pv_is("STIL:TEMP", temp) + + + def test_WHEN_mc_temp_is_set_via_backdoor_THEN_pv_updates(self): + for temp in TEMPERATURE_TEST_VALUES: + self._lewis.backdoor_set_on_device("mc_temp", temp) + self.ca.assert_that_pv_is("MC:TEMP", temp) From a5f8d3584111980f1cc63fbaca42f02654e39544 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 15:39:10 +0000 Subject: [PATCH 27/72] Refactor. --- lewis_emulators/triton/device.py | 19 +++++++++++++-- .../triton/interfaces/stream_interface.py | 23 ++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 059d1a4..63c8f50 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -4,9 +4,12 @@ SUBSYSTEM_NAMES = { - "mixing chamber": "mix_chamber_name", + "mixing chamber": "T0", "stil": "T1", - "heater": "H5" + "sorb": "T9", + "heater": "H5", + "4khx": "T3", + "jthx": "T2" } @@ -46,6 +49,9 @@ def _initialize_data(self): self.stil_temp = 0 self.mc_temp = 0 + self.sorb_temp = 0 + self.fkhx_temp = 0 + self.jthx_temp = 0 def set_valve_state_backdoor(self, valve, newstate): self.valves[int(valve) - 1] = int(newstate) @@ -112,3 +118,12 @@ def get_stil_temp(self): def get_mc_temp(self): return self.mc_temp + + def get_sorb_temp(self): + return self.sorb_temp + + def get_4khx_temp(self): + return self.fkhx_temp + + def get_jthx_temp(self): + return self.jthx_temp diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 8140c45..643a128 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -15,6 +15,8 @@ class TritonStreamInterface(StreamInterface): .escape("READ:SYS:DR:CHAN:MC").build(), CmdBuilder("get_stil_uid") .escape("READ:SYS:DR:CHAN:STIL").build(), + CmdBuilder("get_sorb_uid") + .escape("READ:SYS:DR:CHAN:SORB").build(), # PID setpoints CmdBuilder("set_p") @@ -43,6 +45,12 @@ class TritonStreamInterface(StreamInterface): .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["stil"])).build(), CmdBuilder("get_mc_temp") .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + CmdBuilder("get_sorb_temp") + .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["sorb"])).build(), + CmdBuilder("get_4khx_temp") + .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["4khx"])).build(), + CmdBuilder("get_jthx_temp") + .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["jthx"])).build(), # Heater range CmdBuilder("set_heater_range") @@ -85,7 +93,7 @@ class TritonStreamInterface(StreamInterface): out_terminator = "\r\n" def handle_error(self, request, error): - err = "Request: {}, error: {}." + err = "Request: {}, error: {}.".format(request, error) print(err) self.log.error(err) return err @@ -98,6 +106,10 @@ def get_stil_uid(self): return "STAT:SYS:DR:CHAN:STIL:{}" \ .format(SUBSYSTEM_NAMES["stil"]) + def get_sorb_uid(self): + return "STAT:SYS:DR:CHAN:SORB:{}" \ + .format(SUBSYSTEM_NAMES["sorb"]) + def set_p(self, value): self.device.set_p(float(value)) return "ok" @@ -186,3 +198,12 @@ def get_stil_temp(self): def get_mc_temp(self): return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_mc_temp()) + + def get_sorb_temp(self): + return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["sorb"], self.device.get_sorb_temp()) + + def get_4khx_temp(self): + return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["4khx"], self.device.get_4khx_temp()) + + def get_jthx_temp(self): + return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["jthx"], self.device.get_jthx_temp()) From b465f42e479204da795959a7d020cc3bb698bc8e Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 15:39:22 +0000 Subject: [PATCH 28/72] Refactor. --- tests/triton.py | 212 +++++++++++++++++++++++++----------------------- 1 file changed, 111 insertions(+), 101 deletions(-) diff --git a/tests/triton.py b/tests/triton.py index f55ccf9..739eeb6 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -22,114 +22,124 @@ def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc("triton") self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX) - # def test_WHEN_device_is_started_THEN_it_is_not_disabled(self): - # self.ca.assert_that_pv_is("DISABLE", "COMMS ENABLED") - # - # @skipIf(IOCRegister.uses_rec_sim, "Not implemented in recsim.") - # def test_WHEN_device_is_started_THEN_can_get_mixing_chamber_uid(self): - # self.ca.assert_that_pv_is("MC:UID", "mix_chamber_name") - # - # def test_WHEN_P_setpoint_is_set_THEN_readback_updates(self): - # for value in PID_TEST_VALUES: - # self.ca.assert_setting_setpoint_sets_readback(value, "P") - # - # def test_WHEN_I_setpoint_is_set_THEN_readback_updates(self): - # for value in PID_TEST_VALUES: - # self.ca.assert_setting_setpoint_sets_readback(value, "I") - # - # def test_WHEN_D_setpoint_is_set_THEN_readback_updates(self): - # for value in PID_TEST_VALUES: - # self.ca.assert_setting_setpoint_sets_readback(value, "D") - # - # def test_WHEN_temperature_setpoint_is_set_THEN_readback_updates(self): - # for value in TEMPERATURE_TEST_VALUES: - # self.ca.assert_setting_setpoint_sets_readback(value, set_point_pv="TEMP:SP", readback_pv="TEMP:SP:RBV") - # - # def test_WHEN_heater_range_is_set_THEN_readback_updates(self): - # for value in HEATER_RANGE_TEST_VALUES: - # self.ca.assert_setting_setpoint_sets_readback(value, "HEATER:RANGE") - # - # @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") - # def test_heater_power(self): - # self._lewis.backdoor_set_on_device("heater_power_units", "mA") - # for value in HEATER_RANGE_TEST_VALUES: - # self._lewis.backdoor_set_on_device("heater_power", value) - # self.ca.assert_that_pv_is("HEATER:POWER", value) - # self.ca.assert_that_pv_is("HEATER:POWER.EGU", "mA") - # - # @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") - # def test_WHEN_heater_units_are_set_via_backdoor_THEN_egu_field_on_heater_power_updates_with_the_unit_just_set(self): - # for unit in HEATER_POWER_UNITS: - # self._lewis.backdoor_set_on_device("heater_power_units", unit) - # self.ca.assert_that_pv_is("HEATER:POWER.EGU", unit) - # - # @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") - # def test_WHEN_closed_loop_mode_is_set_via_backdoor_THEN_the_closed_loop_pv_updates_with_value_just_set(self): - # for value in [False, True, False]: # Need to check both transitions work properly - # self._lewis.backdoor_set_on_device("closed_loop", value) - # self.ca.assert_that_pv_is("CLOSEDLOOP", "YES" if value else "NO") - # - # @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") - # def test_WHEN_valve_state_is_set_via_backdoor_THEN_valve_state_pvs_update_with_value_just_set(self): - # for valve in range(1, 11): - # for valve_state_index, valve_state in enumerate(VALVE_STATES): - # self._lewis.backdoor_command(["device", "set_valve_state_backdoor", str(valve), str(valve_state_index)]) - # self.ca.assert_that_pv_is("VALVES:V{}:STATE".format(valve), valve_state) - # - # def test_channels(self): - # for chan in range(1, 7): - # for enabled in [False, True, False]: # Need to check both transitions work properly - # self.ca.assert_setting_setpoint_sets_readback( - # "ON" if enabled else "OFF", "CHANNELS:T{}:STATE".format(chan)) - # - # def test_WHEN_a_short_status_is_set_on_device_THEN_displayed_status_is_identical(self): - # # Status message that could be contained in an EPICS string type - # SHORT_STATUS = "Device status" - # assert 0 < len(SHORT_STATUS) < 40 - # - # # Status message that device is likely to return - longer than EPICS string type but reasonable for a protocol - # MEDIUM_STATUS = "This is a device status that contains a bit more information" - # assert 40 < len(MEDIUM_STATUS) < 256 - # - # # Short and medium statuses should be displayed in full. - # for status in [SHORT_STATUS, MEDIUM_STATUS]: - # self._lewis.backdoor_set_on_device("status", status) - # self.ca.assert_that_pv_is("STATUS", status) - # - # def test_WHEN_long_status_is_set_on_device_THEN_displayed_status_truncated_but_displays_at_least_500_chars(self): - # - # # Somewhat arbitrary, but decide on a minimum number of characters that should be displayed in a - # # status message to the user if the status message is very long. This seems to be a reasonable - # # number given the messages expected, but the manual does not provide an exhaustive list. - # minimum_characters_in_pv = 500 - # - # # Very long status message, used to check that very long messages can be handled gracefully - # LONG_STATUS = "This device status is quite long:" + " (here is a load of information)" * 50 - # - # assert minimum_characters_in_pv < len(LONG_STATUS) - # - # # Allow truncation for long status, but it should still display as many characters as possible - # self._lewis.backdoor_set_on_device("status", LONG_STATUS) - # self.ca.assert_pv_value_causes_func_to_return_true( - # "STATUS", lambda val: LONG_STATUS.startswith(val) and len(val) >= minimum_characters_in_pv) - # - # def test_WHEN_automation_is_set_on_device_THEN_displayed_automation_is_identical(self): - # automations = [ - # "Warming up to 200K", - # "Cooling down to 1K", - # ] - # - # for automation in automations: - # self._lewis.backdoor_set_on_device("automation", automation) - # self.ca.assert_that_pv_is("AUTOMATION", automation) + def test_WHEN_device_is_started_THEN_it_is_not_disabled(self): + self.ca.assert_that_pv_is("DISABLE", "COMMS ENABLED") + + def test_WHEN_P_setpoint_is_set_THEN_readback_updates(self): + for value in PID_TEST_VALUES: + self.ca.assert_setting_setpoint_sets_readback(value, "P") + + def test_WHEN_I_setpoint_is_set_THEN_readback_updates(self): + for value in PID_TEST_VALUES: + self.ca.assert_setting_setpoint_sets_readback(value, "I") + + def test_WHEN_D_setpoint_is_set_THEN_readback_updates(self): + for value in PID_TEST_VALUES: + self.ca.assert_setting_setpoint_sets_readback(value, "D") + + def test_WHEN_temperature_setpoint_is_set_THEN_readback_updates(self): + for value in TEMPERATURE_TEST_VALUES: + self.ca.assert_setting_setpoint_sets_readback(value, set_point_pv="TEMP:SP", readback_pv="TEMP:SP:RBV") + + def test_WHEN_heater_range_is_set_THEN_readback_updates(self): + for value in HEATER_RANGE_TEST_VALUES: + self.ca.assert_setting_setpoint_sets_readback(value, "HEATER:RANGE") + + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + def test_heater_power(self): + self._lewis.backdoor_set_on_device("heater_power_units", "mA") + for value in HEATER_RANGE_TEST_VALUES: + self._lewis.backdoor_set_on_device("heater_power", value) + self.ca.assert_that_pv_is("HEATER:POWER", value) + self.ca.assert_that_pv_is("HEATER:POWER.EGU", "mA") + + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + def test_WHEN_heater_units_are_set_via_backdoor_THEN_egu_field_on_heater_power_updates_with_the_unit_just_set(self): + for unit in HEATER_POWER_UNITS: + self._lewis.backdoor_set_on_device("heater_power_units", unit) + self.ca.assert_that_pv_is("HEATER:POWER.EGU", unit) + + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + def test_WHEN_closed_loop_mode_is_set_via_backdoor_THEN_the_closed_loop_pv_updates_with_value_just_set(self): + for value in [False, True, False]: # Need to check both transitions work properly + self._lewis.backdoor_set_on_device("closed_loop", value) + self.ca.assert_that_pv_is("CLOSEDLOOP", "YES" if value else "NO") + + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + def test_WHEN_valve_state_is_set_via_backdoor_THEN_valve_state_pvs_update_with_value_just_set(self): + for valve in range(1, 11): + for valve_state_index, valve_state in enumerate(VALVE_STATES): + self._lewis.backdoor_command(["device", "set_valve_state_backdoor", str(valve), str(valve_state_index)]) + self.ca.assert_that_pv_is("VALVES:V{}:STATE".format(valve), valve_state) + + def test_channels(self): + for chan in range(1, 7): + for enabled in [False, True, False]: # Need to check both transitions work properly + self.ca.assert_setting_setpoint_sets_readback( + "ON" if enabled else "OFF", "CHANNELS:T{}:STATE".format(chan)) + + def test_WHEN_a_short_status_is_set_on_device_THEN_displayed_status_is_identical(self): + # Status message that could be contained in an EPICS string type + SHORT_STATUS = "Device status" + assert 0 < len(SHORT_STATUS) < 40 + + # Status message that device is likely to return - longer than EPICS string type but reasonable for a protocol + MEDIUM_STATUS = "This is a device status that contains a bit more information" + assert 40 < len(MEDIUM_STATUS) < 256 + + # Short and medium statuses should be displayed in full. + for status in [SHORT_STATUS, MEDIUM_STATUS]: + self._lewis.backdoor_set_on_device("status", status) + self.ca.assert_that_pv_is("STATUS", status) + + def test_WHEN_long_status_is_set_on_device_THEN_displayed_status_truncated_but_displays_at_least_500_chars(self): + + # Somewhat arbitrary, but decide on a minimum number of characters that should be displayed in a + # status message to the user if the status message is very long. This seems to be a reasonable + # number given the messages expected, but the manual does not provide an exhaustive list. + minimum_characters_in_pv = 500 + + # Very long status message, used to check that very long messages can be handled gracefully + LONG_STATUS = "This device status is quite long:" + " (here is a load of information)" * 50 + + assert minimum_characters_in_pv < len(LONG_STATUS) + + # Allow truncation for long status, but it should still display as many characters as possible + self._lewis.backdoor_set_on_device("status", LONG_STATUS) + self.ca.assert_pv_value_causes_func_to_return_true( + "STATUS", lambda val: LONG_STATUS.startswith(val) and len(val) >= minimum_characters_in_pv) + + def test_WHEN_automation_is_set_on_device_THEN_displayed_automation_is_identical(self): + automations = [ + "Warming up to 200K", + "Cooling down to 1K", + ] + + for automation in automations: + self._lewis.backdoor_set_on_device("automation", automation) + self.ca.assert_that_pv_is("AUTOMATION", automation) def test_WHEN_stil_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: self._lewis.backdoor_set_on_device("stil_temp", temp) self.ca.assert_that_pv_is("STIL:TEMP", temp) - def test_WHEN_mc_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: self._lewis.backdoor_set_on_device("mc_temp", temp) self.ca.assert_that_pv_is("MC:TEMP", temp) + + def test_WHEN_sorb_temp_is_set_via_backdoor_THEN_pv_updates(self): + for temp in TEMPERATURE_TEST_VALUES: + self._lewis.backdoor_set_on_device("sorb_temp", temp) + self.ca.assert_that_pv_is("SORB:TEMP", temp) + + def test_WHEN_4KHX_temp_is_set_via_backdoor_THEN_pv_updates(self): + for temp in TEMPERATURE_TEST_VALUES: + self._lewis.backdoor_set_on_device("fkhx_temp", temp) + self.ca.assert_that_pv_is("4KHX:TEMP", temp) + + def test_WHEN_jthx_temp_is_set_via_backdoor_THEN_pv_updates(self): + for temp in TEMPERATURE_TEST_VALUES: + self._lewis.backdoor_set_on_device("jthx_temp", temp) + self.ca.assert_that_pv_is("JTHX:TEMP", temp) From 0884fe1f57aba367d1ea6891bc1ee1c3d2aad0b1 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 16:07:49 +0000 Subject: [PATCH 29/72] Add pressures --- lewis_emulators/triton/device.py | 8 ++++++++ lewis_emulators/triton/interfaces/stream_interface.py | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 63c8f50..8469479 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -53,9 +53,14 @@ def _initialize_data(self): self.fkhx_temp = 0 self.jthx_temp = 0 + self.pressures = [0] * 5 + def set_valve_state_backdoor(self, valve, newstate): self.valves[int(valve) - 1] = int(newstate) + def set_pressure_backdoor(self, valve, newpressure): + self.pressures[int(valve) - 1] = float(newpressure) + def _get_state_handlers(self): return { 'default': DefaultState(), @@ -127,3 +132,6 @@ def get_4khx_temp(self): def get_jthx_temp(self): return self.jthx_temp + + def get_pressure(self, sensor): + return self.pressures[sensor] diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 643a128..81dd79c 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -87,6 +87,10 @@ class TritonStreamInterface(StreamInterface): # Automation CmdBuilder("get_automation") .escape("READ:SYS:DR:ACTN").build(), + + # Pressures + CmdBuilder("get_pressure") + .escape("READ:DEV:P").int().escape(":PRES:SIG:PRES").build(), } in_terminator = "\r\n" @@ -207,3 +211,6 @@ def get_4khx_temp(self): def get_jthx_temp(self): return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["jthx"], self.device.get_jthx_temp()) + + def get_pressure(self, sensor): + return "STAT:DEV:P{}:PRES:SIG:PRES:{}mB".format(sensor, self.device.get_pressure(int(sensor)-1)) From 85b820e998f92069374e231a1c2407ca8ddce99c Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 16:08:02 +0000 Subject: [PATCH 30/72] Add pressures --- tests/triton.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/triton.py b/tests/triton.py index 739eeb6..afd168a 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -9,6 +9,7 @@ PID_TEST_VALUES = 0, 10**-5, 123.45, 10**5 TEMPERATURE_TEST_VALUES = 0, 10**-5, 5.4321, 1000 +PRESSURE_TEST_VALUES = TEMPERATURE_TEST_VALUES HEATER_RANGE_TEST_VALUES = 0.001, 0.316, 1000 HEATER_POWER_UNITS = ["A", "mA", "uA", "nA", "pA"] VALVE_STATES = ["OPEN", "CLOSED", "NOT_FOUND"] @@ -143,3 +144,9 @@ def test_WHEN_jthx_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: self._lewis.backdoor_set_on_device("jthx_temp", temp) self.ca.assert_that_pv_is("JTHX:TEMP", temp) + + def test_WHEN_pressure_is_set_via_backdoor_THEN_pressure_pv_updates(self): + for sensor in [1, 2, 3, 5]: + for pressure in PRESSURE_TEST_VALUES: + self._lewis.backdoor_command(["device", "set_pressure_backdoor", str(sensor), str(pressure)]) + self.ca.assert_that_pv_is("PRESSURE:P{}".format(sensor), pressure) From 704c48d0f87e388f22d16f8442b425a29b212d1f Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 16:59:54 +0000 Subject: [PATCH 31/72] Emulator happy (mostly) to talk to labview --- lewis_emulators/triton/device.py | 2 +- lewis_emulators/triton/interfaces/stream_interface.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 8469479..c5494a1 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -7,7 +7,7 @@ "mixing chamber": "T0", "stil": "T1", "sorb": "T9", - "heater": "H5", + "heater": "T5", "4khx": "T3", "jthx": "T2" } diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 81dd79c..4624187 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -94,13 +94,13 @@ class TritonStreamInterface(StreamInterface): } in_terminator = "\r\n" - out_terminator = "\r\n" + out_terminator = "\n" def handle_error(self, request, error): - err = "Request: {}, error: {}.".format(request, error) - print(err) - self.log.error(err) - return err + err_string = "command was: {}, error was: {}\n".format(request, error) + print(err_string) + # self.log.error(err) + return err_string def get_mc_uid(self): return "STAT:SYS:DR:CHAN:MC:{}" \ From 6357aaa9cd13032b54c92552433243af8a2fd40e Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 17:05:17 +0000 Subject: [PATCH 32/72] mixing chamber must be T5 --- lewis_emulators/triton/device.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index c5494a1..4e5dd5f 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -4,10 +4,10 @@ SUBSYSTEM_NAMES = { - "mixing chamber": "T0", + "mixing chamber": "T5", "stil": "T1", "sorb": "T9", - "heater": "T5", + "heater": "H5", "4khx": "T3", "jthx": "T2" } From f19f526a07e0408d7f063d854558010c9752c3fb Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 21 Dec 2017 09:11:22 +0000 Subject: [PATCH 33/72] Add support for setting closed loop mode --- lewis_emulators/triton/device.py | 6 ++++++ lewis_emulators/triton/interfaces/stream_interface.py | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 4e5dd5f..f9c48ac 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -73,6 +73,12 @@ def _get_transition_handlers(self): return OrderedDict([ ]) + def get_closed_loop_mode(self): + return self.closed_loop + + def set_closed_loop_mode(self, mode): + self.closed_loop = mode + def set_p(self, value): self.p = value diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 4624187..c63bcb6 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -69,6 +69,8 @@ class TritonStreamInterface(StreamInterface): # Loop mode CmdBuilder("get_closed_loop_mode") .escape("READ:DEV:{}:TEMP:LOOP:MODE".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + CmdBuilder("set_closed_loop_mode") + .escape("SET:DEV:{}:TEMP:LOOP:MODE:".format(SUBSYSTEM_NAMES["mixing chamber"])).any().build(), # Valve state CmdBuilder("get_valve_state") @@ -164,7 +166,14 @@ def get_heater_power(self): def get_closed_loop_mode(self): return "STAT:DEV:{}:TEMP:LOOP:MODE:{}"\ - .format(SUBSYSTEM_NAMES["mixing chamber"], "ON" if self.device.closed_loop else "OFF") + .format(SUBSYSTEM_NAMES["mixing chamber"], "ON" if self.device.get_closed_loop_mode() else "OFF") + + def set_closed_loop_mode(self, mode): + if mode not in ["ON", "OFF"]: + raise ValueError("Invalid mode") + + self.device.set_closed_loop_mode(mode == "ON") + return "ok" def get_valve_state(self, valve): From 8b6b12c813607e1054a67d456f2c290a425e4b32 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 21 Dec 2017 09:40:14 +0000 Subject: [PATCH 34/72] Add test for setting closed loop mode --- tests/triton.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/triton.py b/tests/triton.py index afd168a..28eda39 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -150,3 +150,7 @@ def test_WHEN_pressure_is_set_via_backdoor_THEN_pressure_pv_updates(self): for pressure in PRESSURE_TEST_VALUES: self._lewis.backdoor_command(["device", "set_pressure_backdoor", str(sensor), str(pressure)]) self.ca.assert_that_pv_is("PRESSURE:P{}".format(sensor), pressure) + + def test_WHEN_closed_loop_is_set_via_pv_THEN_readback_updates(self): + for state in [False, True, False]: + self.ca.assert_setting_setpoint_sets_readback("YES" if state else "NO", "CLOSEDLOOP") From 6f1e8d752cf7967eb5754913bcf869ff617b1b38 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 21 Dec 2017 11:59:49 +0000 Subject: [PATCH 35/72] Substantial refactor This should allow more flexibility about which channels are present or not --- lewis_emulators/triton/device.py | 96 +++++++++++-------- .../triton/interfaces/stream_interface.py | 85 ++++++---------- 2 files changed, 85 insertions(+), 96 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index f9c48ac..272612a 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -8,8 +8,8 @@ "stil": "T1", "sorb": "T9", "heater": "H5", - "4khx": "T3", - "jthx": "T2" + "fkhx": "T3", + "jthx": "T2", } @@ -22,6 +22,22 @@ class ValveStates(object): NOT_FOUND = 2 +class TemperatureStage(object): + def __init__(self, name): + self.name = name + self.temperature = 0 + self.enabled = True + + self.p = 0 + self.i = 0 + self.d = 0 + + +class PressureSensor(object): + def __init__(self): + self.pressure = 0 + + class SimulatedTriton(StateMachineDevice): def _initialize_data(self): @@ -34,10 +50,6 @@ def _initialize_data(self): self.heater_power = 1 self.heater_power_units = "mA" - self.p = 0 - self.i = 0 - self.d = 0 - self.closed_loop = False self.valves = [ValveStates.CLOSED] * 10 @@ -47,19 +59,31 @@ def _initialize_data(self): self.status = "This is a device status message." self.automation = "This is the automation status" - self.stil_temp = 0 - self.mc_temp = 0 - self.sorb_temp = 0 - self.fkhx_temp = 0 - self.jthx_temp = 0 + self.pressure_sensors = {"P{}".format(idx): PressureSensor() for idx in range(1, 6)} + + self.temperature_stages = { + "T1": TemperatureStage("stil"), + "T2": TemperatureStage("jthx"), + "T3": TemperatureStage("4khx"), + "T4": TemperatureStage("sorb"), + "T5": TemperatureStage("mc"), + } - self.pressures = [0] * 5 + def find_temperature_channel(self, name): + for k, v in self.temperature_stages.items(): + if v.name == name: + return k + else: + raise ValueError("{} not found".format(name)) + + def set_temperature_backdoor(self, stage_name, new_temp): + self.temperature_stages[self.find_temperature_channel(stage_name)].temperature = new_temp def set_valve_state_backdoor(self, valve, newstate): self.valves[int(valve) - 1] = int(newstate) - def set_pressure_backdoor(self, valve, newpressure): - self.pressures[int(valve) - 1] = float(newpressure) + def set_pressure_backdoor(self, sensor, newpressure): + self.pressure_sensors["P{}".format(sensor)].pressure = float(newpressure) def _get_state_handlers(self): return { @@ -79,23 +103,23 @@ def get_closed_loop_mode(self): def set_closed_loop_mode(self, mode): self.closed_loop = mode - def set_p(self, value): - self.p = value + def set_p(self, stage, value): + self.temperature_stages[stage].p = value - def set_i(self, value): - self.i = value + def set_i(self, stage, value): + self.temperature_stages[stage].i = value - def set_d(self, value): - self.d = value + def set_d(self, stage, value): + self.temperature_stages[stage].d = value - def get_p(self): - return self.p + def get_p(self, stage): + return self.temperature_stages[stage].p - def get_i(self): - return self.i + def get_i(self, stage): + return self.temperature_stages[stage].i - def get_d(self): - return self.d + def get_d(self, stage): + return self.temperature_stages[stage].d def get_temperature_setpoint(self): return self.temperature_setpoint @@ -124,20 +148,8 @@ def get_status(self): def get_automation(self): return self.automation - def get_stil_temp(self): - return self.stil_temp - - def get_mc_temp(self): - return self.mc_temp - - def get_sorb_temp(self): - return self.sorb_temp - - def get_4khx_temp(self): - return self.fkhx_temp - - def get_jthx_temp(self): - return self.jthx_temp - def get_pressure(self, sensor): - return self.pressures[sensor] + return self.pressure_sensors[sensor].pressure + + def get_temp(self, stage): + return self.temperature_stages[stage].temperature diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index c63bcb6..cc93b03 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -20,19 +20,19 @@ class TritonStreamInterface(StreamInterface): # PID setpoints CmdBuilder("set_p") - .escape("SET:DEV:{}:TEMP:LOOP:P:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + .escape("SET:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:P:").float().build(), CmdBuilder("set_i") - .escape("SET:DEV:{}:TEMP:LOOP:I:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + .escape("SET:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:I:").float().build(), CmdBuilder("set_d") - .escape("SET:DEV:{}:TEMP:LOOP:D:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + .escape("SET:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:D:").float().build(), # PID readbacks CmdBuilder("get_p") - .escape("READ:DEV:{}:TEMP:LOOP:P".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + .escape("READ:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:P").build(), CmdBuilder("get_i") - .escape("READ:DEV:{}:TEMP:LOOP:I".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + .escape("READ:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:I").build(), CmdBuilder("get_d") - .escape("READ:DEV:{}:TEMP:LOOP:D".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + .escape("READ:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:D").build(), # Setpoint temperature CmdBuilder("set_temperature_setpoint") @@ -41,16 +41,8 @@ class TritonStreamInterface(StreamInterface): .escape("READ:DEV:{}:TEMP:LOOP:TSET".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), # Temperature - CmdBuilder("get_stil_temp") - .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["stil"])).build(), - CmdBuilder("get_mc_temp") - .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), - CmdBuilder("get_sorb_temp") - .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["sorb"])).build(), - CmdBuilder("get_4khx_temp") - .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["4khx"])).build(), - CmdBuilder("get_jthx_temp") - .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["jthx"])).build(), + CmdBuilder("get_temp") + .escape("READ:DEV:").arg("T[0-9]").escape(":TEMP:SIG:TEMP").build(), # Heater range CmdBuilder("set_heater_range") @@ -92,7 +84,7 @@ class TritonStreamInterface(StreamInterface): # Pressures CmdBuilder("get_pressure") - .escape("READ:DEV:P").int().escape(":PRES:SIG:PRES").build(), + .escape("READ:DEV:").arg("P[0-9]").escape(":PRES:SIG:PRES").build(), } in_terminator = "\r\n" @@ -105,40 +97,37 @@ def handle_error(self, request, error): return err_string def get_mc_uid(self): - return "STAT:SYS:DR:CHAN:MC:{}" \ - .format(SUBSYSTEM_NAMES["mixing chamber"]) + return "STAT:SYS:DR:CHAN:MC:{}".format(self.device.find_temperature_channel("mc")) def get_stil_uid(self): - return "STAT:SYS:DR:CHAN:STIL:{}" \ - .format(SUBSYSTEM_NAMES["stil"]) + return "STAT:SYS:DR:CHAN:STIL:{}".format(self.device.find_temperature_channel("stil")) def get_sorb_uid(self): - return "STAT:SYS:DR:CHAN:SORB:{}" \ - .format(SUBSYSTEM_NAMES["sorb"]) + return "STAT:SYS:DR:CHAN:SORB:{}".format(self.device.find_temperature_channel("sorb")) - def set_p(self, value): - self.device.set_p(float(value)) + def set_p(self, stage, value): + self.device.set_p(stage, float(value)) return "ok" - def set_i(self, value): - self.device.set_i(float(value)) + def set_i(self, stage, value): + self.device.set_i(stage, float(value)) return "ok" - def set_d(self, value): - self.device.set_d(float(value)) + def set_d(self, stage, value): + self.device.set_d(stage, float(value)) return "ok" - def get_p(self): + def get_p(self, stage): return "STAT:DEV:{}:TEMP:LOOP:P:{}" \ - .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_p()) + .format(stage, self.device.get_p(stage)) - def get_i(self): + def get_i(self, stage): return "STAT:DEV:{}:TEMP:LOOP:I:{}" \ - .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_i()) + .format(stage, self.device.get_i(stage)) - def get_d(self): + def get_d(self, stage): return "STAT:DEV:{}:TEMP:LOOP:D:{}" \ - .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_d()) + .format(stage, self.device.get_d(stage)) def set_temperature_setpoint(self, value): self.device.set_temperature_setpoint(float(value)) @@ -146,7 +135,7 @@ def set_temperature_setpoint(self, value): def get_temperature_setpoint(self): return "STAT:DEV:{}:TEMP:LOOP:TSET:{}K" \ - .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_temperature_setpoint()) + .format(self.device.find_temperature_channel("mc"), self.device.get_temperature_setpoint()) def set_heater_range(self, value): self.device.set_heater_range(float(value)) @@ -154,11 +143,11 @@ def set_heater_range(self, value): def get_heater_range(self): return "STAT:DEV:{}:TEMP:LOOP:RANGE:{}" \ - .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_heater_range()) + .format(self.device.find_temperature_channel("mc"), self.device.get_heater_range()) def get_heater_type(self): return "STAT:DEV:{}:TEMP:LOOP:HTR:{}" \ - .format(SUBSYSTEM_NAMES["mixing chamber"], SUBSYSTEM_NAMES["heater"]) + .format(self.device.find_temperature_channel("mc"), SUBSYSTEM_NAMES["heater"]) def get_heater_power(self): return "STAT:DEV:{}:HTR:SIG:POWR:{}{}"\ @@ -166,7 +155,7 @@ def get_heater_power(self): def get_closed_loop_mode(self): return "STAT:DEV:{}:TEMP:LOOP:MODE:{}"\ - .format(SUBSYSTEM_NAMES["mixing chamber"], "ON" if self.device.get_closed_loop_mode() else "OFF") + .format(self.device.find_temperature_channel("mc"), "ON" if self.device.get_closed_loop_mode() else "OFF") def set_closed_loop_mode(self, mode): if mode not in ["ON", "OFF"]: @@ -206,20 +195,8 @@ def get_status(self): def get_automation(self): return "STAT:SYS:DR:ACTN:{}".format(self.device.get_automation()) - def get_stil_temp(self): - return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["stil"], self.device.get_stil_temp()) - - def get_mc_temp(self): - return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_mc_temp()) - - def get_sorb_temp(self): - return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["sorb"], self.device.get_sorb_temp()) - - def get_4khx_temp(self): - return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["4khx"], self.device.get_4khx_temp()) - - def get_jthx_temp(self): - return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["jthx"], self.device.get_jthx_temp()) + def get_temp(self, stage): + return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(stage, self.device.get_temp(str(stage))) def get_pressure(self, sensor): - return "STAT:DEV:P{}:PRES:SIG:PRES:{}mB".format(sensor, self.device.get_pressure(int(sensor)-1)) + return "STAT:DEV:{}:PRES:SIG:PRES:{}mB".format(sensor, self.device.get_pressure(sensor)) From b180eade4781358d9b657352af63042107a038f3 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 2 Jan 2018 08:20:27 +0000 Subject: [PATCH 36/72] Rename test to follow GIVEN/WHEN/THEN convention. --- tests/triton.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/tests/triton.py b/tests/triton.py index 28eda39..621a98b 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -8,11 +8,10 @@ DEVICE_PREFIX = "TRITON_01" PID_TEST_VALUES = 0, 10**-5, 123.45, 10**5 -TEMPERATURE_TEST_VALUES = 0, 10**-5, 5.4321, 1000 +TEMPERATURE_TEST_VALUES = 0, 10**-5, 5.4321, 250 PRESSURE_TEST_VALUES = TEMPERATURE_TEST_VALUES HEATER_RANGE_TEST_VALUES = 0.001, 0.316, 1000 HEATER_POWER_UNITS = ["A", "mA", "uA", "nA", "pA"] -VALVE_STATES = ["OPEN", "CLOSED", "NOT_FOUND"] class TritonTests(unittest.TestCase): @@ -47,7 +46,7 @@ def test_WHEN_heater_range_is_set_THEN_readback_updates(self): self.ca.assert_setting_setpoint_sets_readback(value, "HEATER:RANGE") @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") - def test_heater_power(self): + def test_WHEN_heater_power_is_set_via_backdoor_THEN_pv_has_the_value_just_set_and_units_not_changed(self): self._lewis.backdoor_set_on_device("heater_power_units", "mA") for value in HEATER_RANGE_TEST_VALUES: self._lewis.backdoor_set_on_device("heater_power", value) @@ -69,11 +68,11 @@ def test_WHEN_closed_loop_mode_is_set_via_backdoor_THEN_the_closed_loop_pv_updat @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_valve_state_is_set_via_backdoor_THEN_valve_state_pvs_update_with_value_just_set(self): for valve in range(1, 11): - for valve_state_index, valve_state in enumerate(VALVE_STATES): - self._lewis.backdoor_command(["device", "set_valve_state_backdoor", str(valve), str(valve_state_index)]) - self.ca.assert_that_pv_is("VALVES:V{}:STATE".format(valve), valve_state) + for valve_state in [False, True, False]: + self._lewis.backdoor_command(["device", "set_valve_state_backdoor", str(valve), str(valve_state)]) + self.ca.assert_that_pv_is("VALVES:V{}:STATE".format(valve), "OPEN" if valve_state else "CLOSED") - def test_channels(self): + def test_WHEN_channels_are_enabled_and_disabled_via_pv_THEN_the_readback_pv_updates_with_value_just_set(self): for chan in range(1, 7): for enabled in [False, True, False]: # Need to check both transitions work properly self.ca.assert_setting_setpoint_sets_readback( @@ -120,29 +119,32 @@ def test_WHEN_automation_is_set_on_device_THEN_displayed_automation_is_identical self._lewis.backdoor_set_on_device("automation", automation) self.ca.assert_that_pv_is("AUTOMATION", automation) + def _set_temp_via_backdoor(self, channel, temp): + self._lewis.backdoor_command(["device", "set_temperature_backdoor", "'{}'".format(channel), "{}".format(temp)]) + def test_WHEN_stil_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: - self._lewis.backdoor_set_on_device("stil_temp", temp) + self._set_temp_via_backdoor("stil", temp) self.ca.assert_that_pv_is("STIL:TEMP", temp) def test_WHEN_mc_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: - self._lewis.backdoor_set_on_device("mc_temp", temp) + self._set_temp_via_backdoor("mc", temp) self.ca.assert_that_pv_is("MC:TEMP", temp) def test_WHEN_sorb_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: - self._lewis.backdoor_set_on_device("sorb_temp", temp) + self._set_temp_via_backdoor("sorb", temp) self.ca.assert_that_pv_is("SORB:TEMP", temp) def test_WHEN_4KHX_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: - self._lewis.backdoor_set_on_device("fkhx_temp", temp) + self._set_temp_via_backdoor("4khx", temp) self.ca.assert_that_pv_is("4KHX:TEMP", temp) def test_WHEN_jthx_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: - self._lewis.backdoor_set_on_device("jthx_temp", temp) + self._set_temp_via_backdoor("jthx", temp) self.ca.assert_that_pv_is("JTHX:TEMP", temp) def test_WHEN_pressure_is_set_via_backdoor_THEN_pressure_pv_updates(self): From 57a291f81839a9d75461e2fdd678b390fea6abe4 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 2 Jan 2018 13:31:49 +0000 Subject: [PATCH 37/72] Add test for arbitrary command --- tests/triton.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/tests/triton.py b/tests/triton.py index 621a98b..8cac44e 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -80,15 +80,15 @@ def test_WHEN_channels_are_enabled_and_disabled_via_pv_THEN_the_readback_pv_upda def test_WHEN_a_short_status_is_set_on_device_THEN_displayed_status_is_identical(self): # Status message that could be contained in an EPICS string type - SHORT_STATUS = "Device status" - assert 0 < len(SHORT_STATUS) < 40 + short_status = "Device status" + assert 0 < len(short_status) < 40 # Status message that device is likely to return - longer than EPICS string type but reasonable for a protocol - MEDIUM_STATUS = "This is a device status that contains a bit more information" - assert 40 < len(MEDIUM_STATUS) < 256 + medium_status = "This is a device status that contains a bit more information" + assert 40 < len(medium_status) < 256 # Short and medium statuses should be displayed in full. - for status in [SHORT_STATUS, MEDIUM_STATUS]: + for status in [short_status, medium_status]: self._lewis.backdoor_set_on_device("status", status) self.ca.assert_that_pv_is("STATUS", status) @@ -100,14 +100,14 @@ def test_WHEN_long_status_is_set_on_device_THEN_displayed_status_truncated_but_d minimum_characters_in_pv = 500 # Very long status message, used to check that very long messages can be handled gracefully - LONG_STATUS = "This device status is quite long:" + " (here is a load of information)" * 50 + long_status = "This device status is quite long:" + " (here is a load of information)" * 50 - assert minimum_characters_in_pv < len(LONG_STATUS) + assert minimum_characters_in_pv < len(long_status) # Allow truncation for long status, but it should still display as many characters as possible - self._lewis.backdoor_set_on_device("status", LONG_STATUS) + self._lewis.backdoor_set_on_device("status", long_status) self.ca.assert_pv_value_causes_func_to_return_true( - "STATUS", lambda val: LONG_STATUS.startswith(val) and len(val) >= minimum_characters_in_pv) + "STATUS", lambda val: long_status.startswith(val) and len(val) >= minimum_characters_in_pv) def test_WHEN_automation_is_set_on_device_THEN_displayed_automation_is_identical(self): automations = [ @@ -156,3 +156,8 @@ def test_WHEN_pressure_is_set_via_backdoor_THEN_pressure_pv_updates(self): def test_WHEN_closed_loop_is_set_via_pv_THEN_readback_updates(self): for state in [False, True, False]: self.ca.assert_setting_setpoint_sets_readback("YES" if state else "NO", "CLOSEDLOOP") + + def test_WHEN_read_mc_id_is_issued_via_arbitrary_command_THEN_response_is_in_format_device_uses(self): + self.ca.set_pv_value("ARBITRARY:SP", "READ:SYS:DR:CHAN:MC") + self.ca.assert_pv_value_causes_func_to_return_true("ARBITRARY", + lambda val: val.startswith("STAT:SYS:DR:CHAN:MC:")) From e4ec6c580bf4e7ac64987f140612aefc0a2d51c6 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 2 Jan 2018 13:32:18 +0000 Subject: [PATCH 38/72] Refactor emulator --- lewis_emulators/triton/device.py | 91 ++++++----- .../triton/interfaces/stream_interface.py | 148 ++++++++---------- 2 files changed, 123 insertions(+), 116 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 272612a..0e05786 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -3,14 +3,7 @@ from lewis.devices import StateMachineDevice -SUBSYSTEM_NAMES = { - "mixing chamber": "T5", - "stil": "T1", - "sorb": "T9", - "heater": "H5", - "fkhx": "T3", - "jthx": "T2", -} +HEATER_NAME = "H5" class ValveStates(object): @@ -23,43 +16,63 @@ class ValveStates(object): class TemperatureStage(object): + """ + Class representing a temperature stage. + """ def __init__(self, name): self.name = name - self.temperature = 0 + self.temperature = 1 self.enabled = True - self.p = 0 - self.i = 0 - self.d = 0 - class PressureSensor(object): + """ + Class to represent a pressure sensor. + + Having this as a class makes it more extensible in future, as the triton driver is still in flux. + """ def __init__(self): self.pressure = 0 +class Valve(object): + """ + Class to represent a valve. + + Having this as a class makes it more extensible in future, as the triton driver is still in flux. + """ + def __init__(self): + self.open = False + + +class Heater(object): + def __init__(self): + self.range = 0 + self.power = 0 + self.power_units = 0 + + class SimulatedTriton(StateMachineDevice): def _initialize_data(self): """ Initialize all of the device's attributes. """ - self.temperature_setpoint = 0 self.heater_range = 0 - self.heater_power = 1 self.heater_power_units = "mA" + self.temperature_setpoint = 0 + self.p = 0 + self.i = 0 + self.d = 0 self.closed_loop = False - self.valves = [ValveStates.CLOSED] * 10 - - self.channels_enabled = [True] * 6 - self.status = "This is a device status message." self.automation = "This is the automation status" - self.pressure_sensors = {"P{}".format(idx): PressureSensor() for idx in range(1, 6)} + self.valves = {"V{}".format(i): Valve() for i in range(1, 11)} + self.pressure_sensors = {"P{}".format(i): PressureSensor() for i in range(1, 6)} self.temperature_stages = { "T1": TemperatureStage("stil"), @@ -67,6 +80,7 @@ def _initialize_data(self): "T3": TemperatureStage("4khx"), "T4": TemperatureStage("sorb"), "T5": TemperatureStage("mc"), + "T6": TemperatureStage("unknown"), } def find_temperature_channel(self, name): @@ -74,13 +88,13 @@ def find_temperature_channel(self, name): if v.name == name: return k else: - raise ValueError("{} not found".format(name)) + raise KeyError("{} not found".format(name)) def set_temperature_backdoor(self, stage_name, new_temp): self.temperature_stages[self.find_temperature_channel(stage_name)].temperature = new_temp def set_valve_state_backdoor(self, valve, newstate): - self.valves[int(valve) - 1] = int(newstate) + self.valves["V{}".format(valve)].open = bool(newstate) def set_pressure_backdoor(self, sensor, newpressure): self.pressure_sensors["P{}".format(sensor)].pressure = float(newpressure) @@ -103,23 +117,23 @@ def get_closed_loop_mode(self): def set_closed_loop_mode(self, mode): self.closed_loop = mode - def set_p(self, stage, value): - self.temperature_stages[stage].p = value + def set_p(self, value): + self.p = value - def set_i(self, stage, value): - self.temperature_stages[stage].i = value + def set_i(self, value): + self.i = value - def set_d(self, stage, value): - self.temperature_stages[stage].d = value + def set_d(self, value): + self.d = value - def get_p(self, stage): - return self.temperature_stages[stage].p + def get_p(self): + return self.p - def get_i(self, stage): - return self.temperature_stages[stage].i + def get_i(self): + return self.i - def get_d(self, stage): - return self.temperature_stages[stage].d + def get_d(self): + return self.d def get_temperature_setpoint(self): return self.temperature_setpoint @@ -134,13 +148,16 @@ def set_heater_range(self, value): self.heater_range = value def get_valve_state(self, valve): - return self.valves[valve-1] + try: + return ValveStates.OPEN if self.valves[valve].open else ValveStates.CLOSED + except KeyError: + return ValveStates.NOT_FOUND def is_channel_enabled(self, chan): - return self.channels_enabled[chan-1] + return self.temperature_stages[chan].enabled def set_channel_enabled(self, chan, newstate): - self.channels_enabled[chan-1] = newstate + self.temperature_stages[chan].enabled = newstate def get_status(self): return self.status diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index cc93b03..605fcca 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -1,7 +1,7 @@ from lewis.adapters.stream import StreamInterface, Cmd from lewis.core.logging import has_log from lewis_emulators.utils.command_builder import CmdBuilder -from lewis_emulators.triton.device import SUBSYSTEM_NAMES +from lewis_emulators.triton.device import HEATER_NAME from lewis_emulators.triton.device import ValveStates @@ -11,80 +11,55 @@ class TritonStreamInterface(StreamInterface): # Commands that we expect via serial during normal operation commands = { # UIDs - CmdBuilder("get_mc_uid") - .escape("READ:SYS:DR:CHAN:MC").build(), - CmdBuilder("get_stil_uid") - .escape("READ:SYS:DR:CHAN:STIL").build(), - CmdBuilder("get_sorb_uid") - .escape("READ:SYS:DR:CHAN:SORB").build(), + CmdBuilder("get_mc_uid").escape("READ:SYS:DR:CHAN:MC").build(), + CmdBuilder("get_stil_uid").escape("READ:SYS:DR:CHAN:STIL").build(), + CmdBuilder("get_sorb_uid").escape("READ:SYS:DR:CHAN:SORB").build(), # PID setpoints - CmdBuilder("set_p") - .escape("SET:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:P:").float().build(), - CmdBuilder("set_i") - .escape("SET:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:I:").float().build(), - CmdBuilder("set_d") - .escape("SET:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:D:").float().build(), + CmdBuilder("set_p").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:P:").float().build(), + CmdBuilder("set_i").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:I:").float().build(), + CmdBuilder("set_d").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:D:").float().build(), # PID readbacks - CmdBuilder("get_p") - .escape("READ:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:P").build(), - CmdBuilder("get_i") - .escape("READ:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:I").build(), - CmdBuilder("get_d") - .escape("READ:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:D").build(), + CmdBuilder("get_p").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:P").build(), + CmdBuilder("get_i").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:I").build(), + CmdBuilder("get_d").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:D").build(), # Setpoint temperature CmdBuilder("set_temperature_setpoint") - .escape("SET:DEV:{}:TEMP:LOOP:TSET:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), - CmdBuilder("get_temperature_setpoint") - .escape("READ:DEV:{}:TEMP:LOOP:TSET".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + .escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:TSET:").float().build(), + CmdBuilder("get_temperature_setpoint").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:TSET").build(), # Temperature - CmdBuilder("get_temp") - .escape("READ:DEV:").arg("T[0-9]").escape(":TEMP:SIG:TEMP").build(), + CmdBuilder("get_temp").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:SIG:TEMP").build(), # Heater range - CmdBuilder("set_heater_range") - .escape("SET:DEV:{}:TEMP:LOOP:RANGE:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), - CmdBuilder("get_heater_range") - .escape("READ:DEV:{}:TEMP:LOOP:RANGE".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + CmdBuilder("set_heater_range").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:RANGE:").float().build(), + CmdBuilder("get_heater_range").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:RANGE").build(), # Heater type - CmdBuilder("get_heater_type") - .escape("READ:DEV:{}:TEMP:LOOP:HTR".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + CmdBuilder("get_heater_type").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:HTR").build(), # Get heater power - CmdBuilder("get_heater_power") - .escape("READ:DEV:{}:HTR:SIG:POWR".format(SUBSYSTEM_NAMES["heater"])).build(), + CmdBuilder("get_heater_power").escape("READ:DEV:{}:HTR:SIG:POWR".format(HEATER_NAME)).build(), # Loop mode - CmdBuilder("get_closed_loop_mode") - .escape("READ:DEV:{}:TEMP:LOOP:MODE".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), - CmdBuilder("set_closed_loop_mode") - .escape("SET:DEV:{}:TEMP:LOOP:MODE:".format(SUBSYSTEM_NAMES["mixing chamber"])).any().build(), + CmdBuilder("get_closed_loop_mode").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:MODE").build(), + CmdBuilder("set_closed_loop_mode").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:MODE:").any().build(), # Valve state - CmdBuilder("get_valve_state") - .escape("READ:DEV:V").int().escape(":VALV:SIG:STATE").build(), + CmdBuilder("get_valve_state").escape("READ:DEV:").arg("V[0-9]+").escape(":VALV:SIG:STATE").build(), # Channel enablement - CmdBuilder("get_channel_enabled") - .escape("READ:DEV:T").int().escape(":TEMP:MEAS:ENAB").build(), - CmdBuilder("set_channel_enabled") - .escape("SET:DEV:T").int().escape(":TEMP:MEAS:ENAB:").any().build(), + CmdBuilder("get_channel_enabled").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:MEAS:ENAB").build(), + CmdBuilder("set_channel_enabled").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:MEAS:ENAB:").any().build(), # Status - CmdBuilder("get_status") - .escape("READ:SYS:DR:STATUS").build(), - - # Automation - CmdBuilder("get_automation") - .escape("READ:SYS:DR:ACTN").build(), + CmdBuilder("get_status").escape("READ:SYS:DR:STATUS").build(), + CmdBuilder("get_automation").escape("READ:SYS:DR:ACTN").build(), # Pressures - CmdBuilder("get_pressure") - .escape("READ:DEV:").arg("P[0-9]").escape(":PRES:SIG:PRES").build(), + CmdBuilder("get_pressure").escape("READ:DEV:").arg("P[0-9]+").escape(":PRES:SIG:PRES").build(), } in_terminator = "\r\n" @@ -93,9 +68,13 @@ class TritonStreamInterface(StreamInterface): def handle_error(self, request, error): err_string = "command was: {}, error was: {}\n".format(request, error) print(err_string) - # self.log.error(err) + self.log.error(err_string) return err_string + def raise_if_channel_is_not_mc_channel(self, chan): + if str(chan) != self.device.find_temperature_channel("mc"): + raise ValueError("Channel should have been MC channel") + def get_mc_uid(self): return "STAT:SYS:DR:CHAN:MC:{}".format(self.device.find_temperature_channel("mc")) @@ -106,58 +85,66 @@ def get_sorb_uid(self): return "STAT:SYS:DR:CHAN:SORB:{}".format(self.device.find_temperature_channel("sorb")) def set_p(self, stage, value): - self.device.set_p(stage, float(value)) + self.raise_if_channel_is_not_mc_channel(stage) + self.device.set_p(float(value)) return "ok" def set_i(self, stage, value): - self.device.set_i(stage, float(value)) + self.raise_if_channel_is_not_mc_channel(stage) + self.device.set_i(float(value)) return "ok" def set_d(self, stage, value): - self.device.set_d(stage, float(value)) + self.raise_if_channel_is_not_mc_channel(stage) + self.device.set_d(float(value)) return "ok" def get_p(self, stage): - return "STAT:DEV:{}:TEMP:LOOP:P:{}" \ - .format(stage, self.device.get_p(stage)) + self.raise_if_channel_is_not_mc_channel(stage) + return "STAT:DEV:{}:TEMP:LOOP:P:{}".format(stage, self.device.get_p()) def get_i(self, stage): - return "STAT:DEV:{}:TEMP:LOOP:I:{}" \ - .format(stage, self.device.get_i(stage)) + self.raise_if_channel_is_not_mc_channel(stage) + return "STAT:DEV:{}:TEMP:LOOP:I:{}".format(stage, self.device.get_i()) def get_d(self, stage): - return "STAT:DEV:{}:TEMP:LOOP:D:{}" \ - .format(stage, self.device.get_d(stage)) + self.raise_if_channel_is_not_mc_channel(stage) + return "STAT:DEV:{}:TEMP:LOOP:D:{}".format(stage, self.device.get_d()) - def set_temperature_setpoint(self, value): + def set_temperature_setpoint(self, chan, value): + self.raise_if_channel_is_not_mc_channel(chan) self.device.set_temperature_setpoint(float(value)) return "ok" - def get_temperature_setpoint(self): + def get_temperature_setpoint(self, chan): + self.raise_if_channel_is_not_mc_channel(chan) return "STAT:DEV:{}:TEMP:LOOP:TSET:{}K" \ .format(self.device.find_temperature_channel("mc"), self.device.get_temperature_setpoint()) - def set_heater_range(self, value): + def set_heater_range(self, chan, value): + self.raise_if_channel_is_not_mc_channel(chan) self.device.set_heater_range(float(value)) return "ok" - def get_heater_range(self): - return "STAT:DEV:{}:TEMP:LOOP:RANGE:{}" \ - .format(self.device.find_temperature_channel("mc"), self.device.get_heater_range()) + def get_heater_range(self, chan): + self.raise_if_channel_is_not_mc_channel(chan) + return "STAT:DEV:{}:TEMP:LOOP:RANGE:{}".format(chan, self.device.get_heater_range()) - def get_heater_type(self): - return "STAT:DEV:{}:TEMP:LOOP:HTR:{}" \ - .format(self.device.find_temperature_channel("mc"), SUBSYSTEM_NAMES["heater"]) + def get_heater_type(self, chan): + self.raise_if_channel_is_not_mc_channel(chan) + return "STAT:DEV:{}:TEMP:LOOP:HTR:{}".format(chan, HEATER_NAME) def get_heater_power(self): return "STAT:DEV:{}:HTR:SIG:POWR:{}{}"\ - .format(SUBSYSTEM_NAMES["heater"], self.device.heater_power, self.device.heater_power_units) + .format(HEATER_NAME, self.device.heater_power, self.device.heater_power_units) + + def get_closed_loop_mode(self, chan): + self.raise_if_channel_is_not_mc_channel(chan) + return "STAT:DEV:{}:TEMP:LOOP:MODE:{}".format(chan, "ON" if self.device.get_closed_loop_mode() else "OFF") - def get_closed_loop_mode(self): - return "STAT:DEV:{}:TEMP:LOOP:MODE:{}"\ - .format(self.device.find_temperature_channel("mc"), "ON" if self.device.get_closed_loop_mode() else "OFF") + def set_closed_loop_mode(self, chan, mode): + self.raise_if_channel_is_not_mc_channel(chan) - def set_closed_loop_mode(self, mode): if mode not in ["ON", "OFF"]: raise ValueError("Invalid mode") @@ -166,7 +153,7 @@ def set_closed_loop_mode(self, mode): def get_valve_state(self, valve): - state = self.device.get_valve_state(int(valve)) + state = self.device.get_valve_state(valve) if state == ValveStates.CLOSED: response = "CLOSE" @@ -177,16 +164,19 @@ def get_valve_state(self, valve): else: raise ValueError("Invalid valve state: {}".format(state)) - return "STAT:DEV:V{}:VALV:SIG:STATE:{}".format(valve, response) + return "STAT:DEV:{}:VALV:SIG:STATE:{}".format(valve, response) def get_channel_enabled(self, channel): - return "STAT:DEV:T{}:TEMP:MEAS:ENAB:{}"\ - .format(channel, "ON" if self.device.is_channel_enabled(int(channel)) else "OFF") + return "STAT:DEV:{}:TEMP:MEAS:ENAB:{}"\ + .format(channel, "ON" if self.device.is_channel_enabled(channel) else "OFF") def set_channel_enabled(self, channel, newstate): + newstate = str(newstate) + if newstate not in ["ON", "OFF"]: raise ValueError("New state '{}' not valid.".format(newstate)) - self.device.set_channel_enabled(int(channel), str(newstate) == "ON") + + self.device.set_channel_enabled(channel, newstate == "ON") return "ok" def get_status(self): From 9403f5efc7854943f19f0f1e2e291016b77caf19 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 25 Jan 2018 17:08:31 +0000 Subject: [PATCH 39/72] Update tests to be in line with new(er) command set --- tests/triton.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/triton.py b/tests/triton.py index 8cac44e..14c0766 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -124,27 +124,27 @@ def _set_temp_via_backdoor(self, channel, temp): def test_WHEN_stil_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: - self._set_temp_via_backdoor("stil", temp) + self._set_temp_via_backdoor("STIL", temp) self.ca.assert_that_pv_is("STIL:TEMP", temp) def test_WHEN_mc_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: - self._set_temp_via_backdoor("mc", temp) + self._set_temp_via_backdoor("MC", temp) self.ca.assert_that_pv_is("MC:TEMP", temp) def test_WHEN_sorb_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: - self._set_temp_via_backdoor("sorb", temp) + self._set_temp_via_backdoor("SORB", temp) self.ca.assert_that_pv_is("SORB:TEMP", temp) def test_WHEN_4KHX_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: - self._set_temp_via_backdoor("4khx", temp) + self._set_temp_via_backdoor("PT2", temp) self.ca.assert_that_pv_is("4KHX:TEMP", temp) def test_WHEN_jthx_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: - self._set_temp_via_backdoor("jthx", temp) + self._set_temp_via_backdoor("PT1", temp) self.ca.assert_that_pv_is("JTHX:TEMP", temp) def test_WHEN_pressure_is_set_via_backdoor_THEN_pressure_pv_updates(self): From b89dcc368182323ebbd07a275a7fa8bb4d24eb68 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 25 Jan 2018 17:08:47 +0000 Subject: [PATCH 40/72] Update to newer command set --- lewis_emulators/triton/device.py | 15 ++-- .../triton/interfaces/stream_interface.py | 70 +++++++++++-------- 2 files changed, 50 insertions(+), 35 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 0e05786..71d4e4f 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -3,7 +3,7 @@ from lewis.devices import StateMachineDevice -HEATER_NAME = "H5" +HEATER_NAME = "H1" class ValveStates(object): @@ -75,14 +75,17 @@ def _initialize_data(self): self.pressure_sensors = {"P{}".format(i): PressureSensor() for i in range(1, 6)} self.temperature_stages = { - "T1": TemperatureStage("stil"), - "T2": TemperatureStage("jthx"), - "T3": TemperatureStage("4khx"), - "T4": TemperatureStage("sorb"), - "T5": TemperatureStage("mc"), + "T1": TemperatureStage("STIL"), + "T2": TemperatureStage("PT1"), + "T3": TemperatureStage("PT2"), + "T4": TemperatureStage("SORB"), + "T5": TemperatureStage("MC"), "T6": TemperatureStage("unknown"), } + self.sample_channel = "T5" + assert self.sample_channel in self.temperature_stages + def find_temperature_channel(self, name): for k, v in self.temperature_stages.items(): if v.name == name: diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 605fcca..09628f2 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -1,3 +1,5 @@ +from datetime import datetime + from lewis.adapters.stream import StreamInterface, Cmd from lewis.core.logging import has_log from lewis_emulators.utils.command_builder import CmdBuilder @@ -10,10 +12,11 @@ class TritonStreamInterface(StreamInterface): # Commands that we expect via serial during normal operation commands = { + # ID + CmdBuilder("get_idn").escape("*IDN?").build(), + # UIDs - CmdBuilder("get_mc_uid").escape("READ:SYS:DR:CHAN:MC").build(), - CmdBuilder("get_stil_uid").escape("READ:SYS:DR:CHAN:STIL").build(), - CmdBuilder("get_sorb_uid").escape("READ:SYS:DR:CHAN:SORB").build(), + CmdBuilder("get_uid").escape("READ:SYS:DR:CHAN:").arg("[A-Z0-9]+").build(), # PID setpoints CmdBuilder("set_p").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:P:").float().build(), @@ -43,6 +46,9 @@ class TritonStreamInterface(StreamInterface): # Get heater power CmdBuilder("get_heater_power").escape("READ:DEV:{}:HTR:SIG:POWR".format(HEATER_NAME)).build(), + # Heater control sensor + CmdBuilder("get_heater_control_sensor").escape("READ:DEV:{}:HTR:LOOP".format(HEATER_NAME)).build(), + # Loop mode CmdBuilder("get_closed_loop_mode").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:MODE").build(), CmdBuilder("set_closed_loop_mode").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:MODE:").any().build(), @@ -60,6 +66,9 @@ class TritonStreamInterface(StreamInterface): # Pressures CmdBuilder("get_pressure").escape("READ:DEV:").arg("P[0-9]+").escape(":PRES:SIG:PRES").build(), + + # System + CmdBuilder("get_time").escape("READ:SYS:TIME").build(), } in_terminator = "\r\n" @@ -71,67 +80,63 @@ def handle_error(self, request, error): self.log.error(err_string) return err_string - def raise_if_channel_is_not_mc_channel(self, chan): - if str(chan) != self.device.find_temperature_channel("mc"): - raise ValueError("Channel should have been MC channel") + def raise_if_channel_is_not_sample_channel(self, chan): + if str(chan) != self.device.sample_channel: + raise ValueError("Channel should have been sample channel") - def get_mc_uid(self): - return "STAT:SYS:DR:CHAN:MC:{}".format(self.device.find_temperature_channel("mc")) + def get_idn(self): + return "This is the IDN of this device" - def get_stil_uid(self): - return "STAT:SYS:DR:CHAN:STIL:{}".format(self.device.find_temperature_channel("stil")) - - def get_sorb_uid(self): - return "STAT:SYS:DR:CHAN:SORB:{}".format(self.device.find_temperature_channel("sorb")) + def get_uid(self, chan): + return "STAT:SYS:DR:CHAN:{}:{}".format(chan, self.device.find_temperature_channel(chan)) def set_p(self, stage, value): - self.raise_if_channel_is_not_mc_channel(stage) + self.raise_if_channel_is_not_sample_channel(stage) self.device.set_p(float(value)) return "ok" def set_i(self, stage, value): - self.raise_if_channel_is_not_mc_channel(stage) + self.raise_if_channel_is_not_sample_channel(stage) self.device.set_i(float(value)) return "ok" def set_d(self, stage, value): - self.raise_if_channel_is_not_mc_channel(stage) + self.raise_if_channel_is_not_sample_channel(stage) self.device.set_d(float(value)) return "ok" def get_p(self, stage): - self.raise_if_channel_is_not_mc_channel(stage) + self.raise_if_channel_is_not_sample_channel(stage) return "STAT:DEV:{}:TEMP:LOOP:P:{}".format(stage, self.device.get_p()) def get_i(self, stage): - self.raise_if_channel_is_not_mc_channel(stage) + self.raise_if_channel_is_not_sample_channel(stage) return "STAT:DEV:{}:TEMP:LOOP:I:{}".format(stage, self.device.get_i()) def get_d(self, stage): - self.raise_if_channel_is_not_mc_channel(stage) + self.raise_if_channel_is_not_sample_channel(stage) return "STAT:DEV:{}:TEMP:LOOP:D:{}".format(stage, self.device.get_d()) def set_temperature_setpoint(self, chan, value): - self.raise_if_channel_is_not_mc_channel(chan) + self.raise_if_channel_is_not_sample_channel(chan) self.device.set_temperature_setpoint(float(value)) return "ok" def get_temperature_setpoint(self, chan): - self.raise_if_channel_is_not_mc_channel(chan) - return "STAT:DEV:{}:TEMP:LOOP:TSET:{}K" \ - .format(self.device.find_temperature_channel("mc"), self.device.get_temperature_setpoint()) + self.raise_if_channel_is_not_sample_channel(chan) + return "STAT:DEV:{}:TEMP:LOOP:TSET:{}K" .format(chan, self.device.get_temperature_setpoint()) def set_heater_range(self, chan, value): - self.raise_if_channel_is_not_mc_channel(chan) + self.raise_if_channel_is_not_sample_channel(chan) self.device.set_heater_range(float(value)) return "ok" def get_heater_range(self, chan): - self.raise_if_channel_is_not_mc_channel(chan) - return "STAT:DEV:{}:TEMP:LOOP:RANGE:{}".format(chan, self.device.get_heater_range()) + self.raise_if_channel_is_not_sample_channel(chan) + return "STAT:DEV:{}:TEMP:LOOP:RANGE:{}mA".format(chan, self.device.get_heater_range()) def get_heater_type(self, chan): - self.raise_if_channel_is_not_mc_channel(chan) + self.raise_if_channel_is_not_sample_channel(chan) return "STAT:DEV:{}:TEMP:LOOP:HTR:{}".format(chan, HEATER_NAME) def get_heater_power(self): @@ -139,11 +144,11 @@ def get_heater_power(self): .format(HEATER_NAME, self.device.heater_power, self.device.heater_power_units) def get_closed_loop_mode(self, chan): - self.raise_if_channel_is_not_mc_channel(chan) + self.raise_if_channel_is_not_sample_channel(chan) return "STAT:DEV:{}:TEMP:LOOP:MODE:{}".format(chan, "ON" if self.device.get_closed_loop_mode() else "OFF") def set_closed_loop_mode(self, chan, mode): - self.raise_if_channel_is_not_mc_channel(chan) + self.raise_if_channel_is_not_sample_channel(chan) if mode not in ["ON", "OFF"]: raise ValueError("Invalid mode") @@ -190,3 +195,10 @@ def get_temp(self, stage): def get_pressure(self, sensor): return "STAT:DEV:{}:PRES:SIG:PRES:{}mB".format(sensor, self.device.get_pressure(sensor)) + + def get_time(self): + return datetime.now().strftime("STAT:SYS:TIME:%H:%M:%S") + + def get_heater_control_sensor(self): + # Always assume heater controls sample. This is true so far at ISIS + return "STAT:DEV:{}:HTR:LOOP:SENS:{}".format(HEATER_NAME, self.device.sample_channel) From 47a3aa97d7bad22b8b1e913255fd48a0ef40ffcc Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 30 Jan 2018 16:07:12 +0000 Subject: [PATCH 41/72] Recsim tests --- tests/triton.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/tests/triton.py b/tests/triton.py index 14c0766..a901618 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -11,8 +11,14 @@ TEMPERATURE_TEST_VALUES = 0, 10**-5, 5.4321, 250 PRESSURE_TEST_VALUES = TEMPERATURE_TEST_VALUES HEATER_RANGE_TEST_VALUES = 0.001, 0.316, 1000 +RESISTANCE_TEST_VALUES = 10, 3456 +EXCITATION_TEST_VALUES = PID_TEST_VALUES +TIME_DELAY_TEST_VALUES = RESISTANCE_TEST_VALUES + HEATER_POWER_UNITS = ["A", "mA", "uA", "nA", "pA"] +VALID_TEMPERATURE_SENSORS = [i for i in range(0, 6)] + class TritonTests(unittest.TestCase): """ @@ -63,7 +69,7 @@ def test_WHEN_heater_units_are_set_via_backdoor_THEN_egu_field_on_heater_power_u def test_WHEN_closed_loop_mode_is_set_via_backdoor_THEN_the_closed_loop_pv_updates_with_value_just_set(self): for value in [False, True, False]: # Need to check both transitions work properly self._lewis.backdoor_set_on_device("closed_loop", value) - self.ca.assert_that_pv_is("CLOSEDLOOP", "YES" if value else "NO") + self.ca.assert_that_pv_is("CLOSEDLOOP", "On" if value else "Off") @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_valve_state_is_set_via_backdoor_THEN_valve_state_pvs_update_with_value_just_set(self): @@ -72,12 +78,14 @@ def test_WHEN_valve_state_is_set_via_backdoor_THEN_valve_state_pvs_update_with_v self._lewis.backdoor_command(["device", "set_valve_state_backdoor", str(valve), str(valve_state)]) self.ca.assert_that_pv_is("VALVES:V{}:STATE".format(valve), "OPEN" if valve_state else "CLOSED") + @skipIf(IOCRegister.uses_rec_sim, "Behaviour too complex for recsim") def test_WHEN_channels_are_enabled_and_disabled_via_pv_THEN_the_readback_pv_updates_with_value_just_set(self): - for chan in range(1, 7): + for chan in VALID_TEMPERATURE_SENSORS: for enabled in [False, True, False]: # Need to check both transitions work properly self.ca.assert_setting_setpoint_sets_readback( "ON" if enabled else "OFF", "CHANNELS:T{}:STATE".format(chan)) + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_a_short_status_is_set_on_device_THEN_displayed_status_is_identical(self): # Status message that could be contained in an EPICS string type short_status = "Device status" @@ -92,6 +100,7 @@ def test_WHEN_a_short_status_is_set_on_device_THEN_displayed_status_is_identical self._lewis.backdoor_set_on_device("status", status) self.ca.assert_that_pv_is("STATUS", status) + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_long_status_is_set_on_device_THEN_displayed_status_truncated_but_displays_at_least_500_chars(self): # Somewhat arbitrary, but decide on a minimum number of characters that should be displayed in a @@ -109,6 +118,7 @@ def test_WHEN_long_status_is_set_on_device_THEN_displayed_status_truncated_but_d self.ca.assert_pv_value_causes_func_to_return_true( "STATUS", lambda val: long_status.startswith(val) and len(val) >= minimum_characters_in_pv) + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_automation_is_set_on_device_THEN_displayed_automation_is_identical(self): automations = [ "Warming up to 200K", @@ -122,31 +132,37 @@ def test_WHEN_automation_is_set_on_device_THEN_displayed_automation_is_identical def _set_temp_via_backdoor(self, channel, temp): self._lewis.backdoor_command(["device", "set_temperature_backdoor", "'{}'".format(channel), "{}".format(temp)]) + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_stil_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: self._set_temp_via_backdoor("STIL", temp) self.ca.assert_that_pv_is("STIL:TEMP", temp) + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_mc_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: self._set_temp_via_backdoor("MC", temp) self.ca.assert_that_pv_is("MC:TEMP", temp) + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_sorb_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: self._set_temp_via_backdoor("SORB", temp) self.ca.assert_that_pv_is("SORB:TEMP", temp) + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_4KHX_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: self._set_temp_via_backdoor("PT2", temp) self.ca.assert_that_pv_is("4KHX:TEMP", temp) + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_jthx_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: self._set_temp_via_backdoor("PT1", temp) self.ca.assert_that_pv_is("JTHX:TEMP", temp) + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_pressure_is_set_via_backdoor_THEN_pressure_pv_updates(self): for sensor in [1, 2, 3, 5]: for pressure in PRESSURE_TEST_VALUES: @@ -155,9 +171,55 @@ def test_WHEN_pressure_is_set_via_backdoor_THEN_pressure_pv_updates(self): def test_WHEN_closed_loop_is_set_via_pv_THEN_readback_updates(self): for state in [False, True, False]: - self.ca.assert_setting_setpoint_sets_readback("YES" if state else "NO", "CLOSEDLOOP") + self.ca.assert_setting_setpoint_sets_readback("On" if state else "Off", "CLOSEDLOOP") + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_read_mc_id_is_issued_via_arbitrary_command_THEN_response_is_in_format_device_uses(self): self.ca.set_pv_value("ARBITRARY:SP", "READ:SYS:DR:CHAN:MC") self.ca.assert_pv_value_causes_func_to_return_true("ARBITRARY", lambda val: val.startswith("STAT:SYS:DR:CHAN:MC:")) + + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + def test_WHEN_channel_temperature_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written(self): + for chan in VALID_TEMPERATURE_SENSORS: + for value in TEMPERATURE_TEST_VALUES: + self._lewis.backdoor_command( + ["device", "set_sensor_property_backdoor", str(chan), "temperature", str(value)] + ) + self.ca.assert_that_pv_is("CHANNELS:T{}:TEMP".format(chan), value) + + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + def test_WHEN_channel_resistance_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written(self): + for chan in VALID_TEMPERATURE_SENSORS: + for value in RESISTANCE_TEST_VALUES: + self._lewis.backdoor_command( + ["device", "set_sensor_property_backdoor", str(chan), "resistance", str(value)] + ) + self.ca.assert_that_pv_is("CHANNELS:T{}:RES".format(chan), value) + + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + def test_WHEN_channel_excitation_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written(self): + for chan in VALID_TEMPERATURE_SENSORS: + for value in EXCITATION_TEST_VALUES: + self._lewis.backdoor_command( + ["device", "set_sensor_property_backdoor", str(chan), "excitation", str(value)] + ) + self.ca.assert_that_pv_is("CHANNELS:T{}:EXCITATION".format(chan), value) + + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + def test_WHEN_channel_pause_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written(self): + for chan in VALID_TEMPERATURE_SENSORS: + for value in TIME_DELAY_TEST_VALUES: + self._lewis.backdoor_command( + ["device", "set_sensor_property_backdoor", str(chan), "pause", str(value)] + ) + self.ca.assert_that_pv_is("CHANNELS:T{}:PAUSE".format(chan), value) + + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + def test_WHEN_channel_dwell_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written(self): + for chan in VALID_TEMPERATURE_SENSORS: + for value in TIME_DELAY_TEST_VALUES: + self._lewis.backdoor_command( + ["device", "set_sensor_property_backdoor", str(chan), "dwell", str(value)] + ) + self.ca.assert_that_pv_is("CHANNELS:T{}:DWELL".format(chan), value) From 0cf676819a37938e2ffde347a772412cdae8c5f9 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 30 Jan 2018 17:19:14 +0000 Subject: [PATCH 42/72] Emulator fixes --- lewis_emulators/triton/device.py | 26 ++++-- .../triton/interfaces/stream_interface.py | 81 ++++++++++++------- 2 files changed, 73 insertions(+), 34 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 71d4e4f..dbdeda7 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -24,6 +24,14 @@ def __init__(self, name): self.temperature = 1 self.enabled = True + self.resistance = 0 + + self.excitation_type = "VOLT" + self.excitation = 10 + + self.pause = 10 + self.dwell = 3 + class PressureSensor(object): """ @@ -87,6 +95,7 @@ def _initialize_data(self): assert self.sample_channel in self.temperature_stages def find_temperature_channel(self, name): + for k, v in self.temperature_stages.items(): if v.name == name: return k @@ -102,17 +111,19 @@ def set_valve_state_backdoor(self, valve, newstate): def set_pressure_backdoor(self, sensor, newpressure): self.pressure_sensors["P{}".format(sensor)].pressure = float(newpressure) + def set_sensor_property_backdoor(self, sensor, property, value): + # The sensor + 1 is due to an indexing error in the Oxford Instruments firmware. + # We are emulating this off-by-one error. + setattr(self.temperature_stages["T{}".format(sensor+1)], property, value) + def _get_state_handlers(self): - return { - 'default': DefaultState(), - } + return {'default': DefaultState()} def _get_initial_state(self): return 'default' def _get_transition_handlers(self): - return OrderedDict([ - ]) + return OrderedDict([]) def get_closed_loop_mode(self): return self.closed_loop @@ -157,7 +168,10 @@ def get_valve_state(self, valve): return ValveStates.NOT_FOUND def is_channel_enabled(self, chan): - return self.temperature_stages[chan].enabled + try: + return self.temperature_stages[chan].enabled + except KeyError: + return False def set_channel_enabled(self, chan, newstate): self.temperature_stages[chan].enabled = newstate diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 09628f2..c34cd67 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -13,69 +13,73 @@ class TritonStreamInterface(StreamInterface): # Commands that we expect via serial during normal operation commands = { # ID - CmdBuilder("get_idn").escape("*IDN?").build(), + CmdBuilder("get_idn").escape("*IDN?").eos().build(), # UIDs - CmdBuilder("get_uid").escape("READ:SYS:DR:CHAN:").arg("[A-Z0-9]+").build(), + CmdBuilder("get_uid").escape("READ:SYS:DR:CHAN:").arg("[A-Z0-9]+").eos().build(), # PID setpoints - CmdBuilder("set_p").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:P:").float().build(), - CmdBuilder("set_i").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:I:").float().build(), - CmdBuilder("set_d").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:D:").float().build(), + CmdBuilder("set_p").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:P:").float().eos().build(), + CmdBuilder("set_i").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:I:").float().eos().build(), + CmdBuilder("set_d").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:D:").float().eos().build(), # PID readbacks - CmdBuilder("get_p").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:P").build(), - CmdBuilder("get_i").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:I").build(), - CmdBuilder("get_d").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:D").build(), + CmdBuilder("get_p").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:P").eos().build(), + CmdBuilder("get_i").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:I").eos().build(), + CmdBuilder("get_d").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:D").eos().build(), # Setpoint temperature - CmdBuilder("set_temperature_setpoint") - .escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:TSET:").float().build(), - CmdBuilder("get_temperature_setpoint").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:TSET").build(), + CmdBuilder("set_temperature_setpoint").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:TSET:").float().eos().build(), + CmdBuilder("get_temperature_setpoint").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:TSET").eos().build(), # Temperature - CmdBuilder("get_temp").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:SIG:TEMP").build(), + CmdBuilder("get_temp").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:SIG:TEMP").eos().build(), # Heater range - CmdBuilder("set_heater_range").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:RANGE:").float().build(), - CmdBuilder("get_heater_range").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:RANGE").build(), + CmdBuilder("set_heater_range").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:RANGE:").float().eos().build(), + CmdBuilder("get_heater_range").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:RANGE").eos().build(), # Heater type - CmdBuilder("get_heater_type").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:HTR").build(), + CmdBuilder("get_heater_type").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:HTR").eos().build(), # Get heater power - CmdBuilder("get_heater_power").escape("READ:DEV:{}:HTR:SIG:POWR".format(HEATER_NAME)).build(), + CmdBuilder("get_heater_power").escape("READ:DEV:{}:HTR:SIG:POWR".format(HEATER_NAME)).eos().build(), # Heater control sensor - CmdBuilder("get_heater_control_sensor").escape("READ:DEV:{}:HTR:LOOP".format(HEATER_NAME)).build(), + CmdBuilder("get_heater_control_sensor").escape("READ:DEV:{}:HTR:LOOP".format(HEATER_NAME)).eos().build(), # Loop mode - CmdBuilder("get_closed_loop_mode").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:MODE").build(), - CmdBuilder("set_closed_loop_mode").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:MODE:").any().build(), + CmdBuilder("get_closed_loop_mode").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:MODE").eos().build(), + CmdBuilder("set_closed_loop_mode").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:MODE:").any().eos().build(), # Valve state - CmdBuilder("get_valve_state").escape("READ:DEV:").arg("V[0-9]+").escape(":VALV:SIG:STATE").build(), + CmdBuilder("get_valve_state").escape("READ:DEV:").arg("V[0-9]+").escape(":VALV:SIG:STATE").eos().build(), # Channel enablement - CmdBuilder("get_channel_enabled").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:MEAS:ENAB").build(), - CmdBuilder("set_channel_enabled").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:MEAS:ENAB:").any().build(), + CmdBuilder("get_channel_enabled").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:MEAS:ENAB").eos().build(), + CmdBuilder("set_channel_enabled").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:MEAS:ENAB:").any().eos().build(), # Status - CmdBuilder("get_status").escape("READ:SYS:DR:STATUS").build(), - CmdBuilder("get_automation").escape("READ:SYS:DR:ACTN").build(), + CmdBuilder("get_status").escape("READ:SYS:DR:STATUS").eos().build(), + CmdBuilder("get_automation").escape("READ:SYS:DR:ACTN").eos().build(), # Pressures - CmdBuilder("get_pressure").escape("READ:DEV:").arg("P[0-9]+").escape(":PRES:SIG:PRES").build(), + CmdBuilder("get_pressure").escape("READ:DEV:").arg("P[0-9]+").escape(":PRES:SIG:PRES").eos().build(), # System - CmdBuilder("get_time").escape("READ:SYS:TIME").build(), + CmdBuilder("get_time").escape("READ:SYS:TIME").eos().build(), + + # Sensor info + CmdBuilder("get_sig").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:SIG").eos().build(), + CmdBuilder("get_excitation").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:EXCT").eos().build(), + CmdBuilder("get_meas").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:MEAS").eos().build(), } in_terminator = "\r\n" out_terminator = "\n" def handle_error(self, request, error): - err_string = "command was: {}, error was: {}\n".format(request, error) + err_string = "command was: {}, error was: {}: {}\n".format(request, error.__class__.__name__, error) print(err_string) self.log.error(err_string) return err_string @@ -154,7 +158,7 @@ def set_closed_loop_mode(self, chan, mode): raise ValueError("Invalid mode") self.device.set_closed_loop_mode(mode == "ON") - return "ok" + return "STAT:SET:DEV:{}:TEMP:LOOP:MODE:{}:VALID".format(chan, mode) def get_valve_state(self, valve): @@ -202,3 +206,24 @@ def get_time(self): def get_heater_control_sensor(self): # Always assume heater controls sample. This is true so far at ISIS return "STAT:DEV:{}:HTR:LOOP:SENS:{}".format(HEATER_NAME, self.device.sample_channel) + + def get_sig(self, chan): + return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K:RES:{}Ohm".format( + chan, + self.device.temperature_stages[chan].temperature, + self.device.temperature_stages[chan].resistance, + ) + + def get_excitation(self, chan): + return "STAT:DEV:{}:TEMP:EXCT:TYPE:{}:MAG:{}V".format( + chan, + self.device.temperature_stages[chan].excitation_type, + self.device.temperature_stages[chan].excitation, + ) + + def get_meas(self, chan): + return "STAT:DEV:{}:TEMP:MEAS:PAUS:{}s:DWEL:{}s:ENAB:ON".format( + chan, + self.device.temperature_stages[chan].pause, + self.device.temperature_stages[chan].dwell, + ) From ffcb71070094fead95a43747b72643a5c8d73581 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 6 Feb 2018 11:24:07 +0000 Subject: [PATCH 43/72] Add heater % --- lewis_emulators/triton/device.py | 2 +- lewis_emulators/triton/interfaces/stream_interface.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index dbdeda7..5c8b923 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -68,7 +68,7 @@ def _initialize_data(self): """ self.heater_range = 0 self.heater_power = 1 - self.heater_power_units = "mA" + self.heater_current = 0 self.temperature_setpoint = 0 self.p = 0 diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index c34cd67..7972172 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -45,6 +45,9 @@ class TritonStreamInterface(StreamInterface): # Get heater power CmdBuilder("get_heater_power").escape("READ:DEV:{}:HTR:SIG:POWR".format(HEATER_NAME)).eos().build(), + # Get heater current + CmdBuilder("get_heater_current").escape("READ:DEV:{}:HTR:SIG:CURR".format(HEATER_NAME)).eos().build(), + # Heater control sensor CmdBuilder("get_heater_control_sensor").escape("READ:DEV:{}:HTR:LOOP".format(HEATER_NAME)).eos().build(), @@ -144,8 +147,10 @@ def get_heater_type(self, chan): return "STAT:DEV:{}:TEMP:LOOP:HTR:{}".format(chan, HEATER_NAME) def get_heater_power(self): - return "STAT:DEV:{}:HTR:SIG:POWR:{}{}"\ - .format(HEATER_NAME, self.device.heater_power, self.device.heater_power_units) + return "STAT:DEV:{}:HTR:SIG:POWR:{}uW".format(HEATER_NAME, self.device.heater_power) + + def get_heater_current(self): + return "STAT:DEV:{}:HTR:SIG:CURR:{}mA".format(HEATER_NAME, self.device.heater_current) def get_closed_loop_mode(self, chan): self.raise_if_channel_is_not_sample_channel(chan) From 8e5d7079d446c69eed90261530558398452cd8df Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 6 Feb 2018 11:24:25 +0000 Subject: [PATCH 44/72] Add tests for heater % --- tests/triton.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/tests/triton.py b/tests/triton.py index a901618..131dc42 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -15,8 +15,6 @@ EXCITATION_TEST_VALUES = PID_TEST_VALUES TIME_DELAY_TEST_VALUES = RESISTANCE_TEST_VALUES -HEATER_POWER_UNITS = ["A", "mA", "uA", "nA", "pA"] - VALID_TEMPERATURE_SENSORS = [i for i in range(0, 6)] @@ -52,18 +50,10 @@ def test_WHEN_heater_range_is_set_THEN_readback_updates(self): self.ca.assert_setting_setpoint_sets_readback(value, "HEATER:RANGE") @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") - def test_WHEN_heater_power_is_set_via_backdoor_THEN_pv_has_the_value_just_set_and_units_not_changed(self): - self._lewis.backdoor_set_on_device("heater_power_units", "mA") + def test_WHEN_heater_power_is_set_via_backdoor_THEN_pv_has_the_value_just_set(self): for value in HEATER_RANGE_TEST_VALUES: self._lewis.backdoor_set_on_device("heater_power", value) self.ca.assert_that_pv_is("HEATER:POWER", value) - self.ca.assert_that_pv_is("HEATER:POWER.EGU", "mA") - - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") - def test_WHEN_heater_units_are_set_via_backdoor_THEN_egu_field_on_heater_power_updates_with_the_unit_just_set(self): - for unit in HEATER_POWER_UNITS: - self._lewis.backdoor_set_on_device("heater_power_units", unit) - self.ca.assert_that_pv_is("HEATER:POWER.EGU", unit) @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_closed_loop_mode_is_set_via_backdoor_THEN_the_closed_loop_pv_updates_with_value_just_set(self): @@ -223,3 +213,19 @@ def test_WHEN_channel_dwell_is_set_via_backdoor_THEN_the_pvs_update_with_values_ ["device", "set_sensor_property_backdoor", str(chan), "dwell", str(value)] ) self.ca.assert_that_pv_is("CHANNELS:T{}:DWELL".format(chan), value) + + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + def test_WHEN_heater_heater_current_is_set_via_backdoor_THEN_pv_updates_with_new_value(self): + for curr in HEATER_RANGE_TEST_VALUES: + self._lewis.backdoor_set_on_device("heater_current", curr) + self.ca.assert_that_pv_is_number("HEATER:CURR", curr) + + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + def test_WHEN_heater_power_and_range_are_changed_THEN_heater_percent_power_is_calculated_correctly(self): + for heater_range in HEATER_RANGE_TEST_VALUES: + for current in HEATER_RANGE_TEST_VALUES: + self._lewis.backdoor_set_on_device("heater_current", current) + self._lewis.backdoor_set_on_device("heater_range", heater_range) + + assert heater_range != 0, "Heater range of zero will cause a zero division error!" + self.ca.assert_that_pv_is_number("HEATER:PERCENT", 100*current/heater_range, tolerance=0.05) From 1d440419dc92ffbac2701fceaffb821351b3cae3 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 7 Feb 2018 16:14:41 +0000 Subject: [PATCH 45/72] Modify command for calculating heater % --- lewis_emulators/triton/device.py | 9 +-------- lewis_emulators/triton/interfaces/stream_interface.py | 7 +++++-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 5c8b923..39a2d8a 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -53,13 +53,6 @@ def __init__(self): self.open = False -class Heater(object): - def __init__(self): - self.range = 0 - self.power = 0 - self.power_units = 0 - - class SimulatedTriton(StateMachineDevice): def _initialize_data(self): @@ -68,7 +61,7 @@ def _initialize_data(self): """ self.heater_range = 0 self.heater_power = 1 - self.heater_current = 0 + self.heater_resistance = 0 self.temperature_setpoint = 0 self.p = 0 diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 7972172..ddfb1c6 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -45,8 +45,8 @@ class TritonStreamInterface(StreamInterface): # Get heater power CmdBuilder("get_heater_power").escape("READ:DEV:{}:HTR:SIG:POWR".format(HEATER_NAME)).eos().build(), - # Get heater current - CmdBuilder("get_heater_current").escape("READ:DEV:{}:HTR:SIG:CURR".format(HEATER_NAME)).eos().build(), + # Get heater resistance + CmdBuilder("get_heater_resistance").escape("READ:DEV:{}:HTR:RES".format(HEATER_NAME)).eos().build(), # Heater control sensor CmdBuilder("get_heater_control_sensor").escape("READ:DEV:{}:HTR:LOOP".format(HEATER_NAME)).eos().build(), @@ -149,6 +149,9 @@ def get_heater_type(self, chan): def get_heater_power(self): return "STAT:DEV:{}:HTR:SIG:POWR:{}uW".format(HEATER_NAME, self.device.heater_power) + def get_heater_resistance(self): + return "STAT:DEV:{}:HTR:RES:{}Ohm".format(HEATER_NAME, self.device.heater_resistance) + def get_heater_current(self): return "STAT:DEV:{}:HTR:SIG:CURR:{}mA".format(HEATER_NAME, self.device.heater_current) From 4a468265f061f94529dd13f680db5d4301a64ded Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 7 Feb 2018 16:14:59 +0000 Subject: [PATCH 46/72] Add tests for new command for getting heater % --- tests/triton.py | 89 ++++++++++++++++++++++++------------------------- 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/tests/triton.py b/tests/triton.py index 131dc42..dfb17b1 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -1,5 +1,8 @@ +from __future__ import division + import unittest from unittest import skipIf +import itertools from utils.channel_access import ChannelAccess from utils.ioc_launcher import IOCRegister @@ -10,12 +13,14 @@ PID_TEST_VALUES = 0, 10**-5, 123.45, 10**5 TEMPERATURE_TEST_VALUES = 0, 10**-5, 5.4321, 250 PRESSURE_TEST_VALUES = TEMPERATURE_TEST_VALUES +POWER_TEST_VALUES = TEMPERATURE_TEST_VALUES HEATER_RANGE_TEST_VALUES = 0.001, 0.316, 1000 RESISTANCE_TEST_VALUES = 10, 3456 EXCITATION_TEST_VALUES = PID_TEST_VALUES TIME_DELAY_TEST_VALUES = RESISTANCE_TEST_VALUES VALID_TEMPERATURE_SENSORS = [i for i in range(0, 6)] +VALID_PRESSURE_SENSORS = [i for i in range(1, 6)] class TritonTests(unittest.TestCase): @@ -154,10 +159,9 @@ def test_WHEN_jthx_temp_is_set_via_backdoor_THEN_pv_updates(self): @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_pressure_is_set_via_backdoor_THEN_pressure_pv_updates(self): - for sensor in [1, 2, 3, 5]: - for pressure in PRESSURE_TEST_VALUES: - self._lewis.backdoor_command(["device", "set_pressure_backdoor", str(sensor), str(pressure)]) - self.ca.assert_that_pv_is("PRESSURE:P{}".format(sensor), pressure) + for sensor, pressure in itertools.product(VALID_PRESSURE_SENSORS, PRESSURE_TEST_VALUES): + self._lewis.backdoor_command(["device", "set_pressure_backdoor", str(sensor), str(pressure)]) + self.ca.assert_that_pv_is("PRESSURE:P{}".format(sensor), pressure) def test_WHEN_closed_loop_is_set_via_pv_THEN_readback_updates(self): for state in [False, True, False]: @@ -171,61 +175,56 @@ def test_WHEN_read_mc_id_is_issued_via_arbitrary_command_THEN_response_is_in_for @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_channel_temperature_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written(self): - for chan in VALID_TEMPERATURE_SENSORS: - for value in TEMPERATURE_TEST_VALUES: - self._lewis.backdoor_command( - ["device", "set_sensor_property_backdoor", str(chan), "temperature", str(value)] - ) - self.ca.assert_that_pv_is("CHANNELS:T{}:TEMP".format(chan), value) + for chan, value in itertools.product(VALID_TEMPERATURE_SENSORS, TEMPERATURE_TEST_VALUES): + self._lewis.backdoor_command(["device", "set_sensor_property_backdoor", str(chan), "temperature", str(value)]) + self.ca.assert_that_pv_is("CHANNELS:T{}:TEMP".format(chan), value) @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_channel_resistance_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written(self): - for chan in VALID_TEMPERATURE_SENSORS: - for value in RESISTANCE_TEST_VALUES: - self._lewis.backdoor_command( - ["device", "set_sensor_property_backdoor", str(chan), "resistance", str(value)] - ) - self.ca.assert_that_pv_is("CHANNELS:T{}:RES".format(chan), value) + for chan, value in itertools.product(VALID_TEMPERATURE_SENSORS, RESISTANCE_TEST_VALUES): + self._lewis.backdoor_command(["device", "set_sensor_property_backdoor", str(chan), "resistance", str(value)]) + self.ca.assert_that_pv_is("CHANNELS:T{}:RES".format(chan), value) @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_channel_excitation_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written(self): - for chan in VALID_TEMPERATURE_SENSORS: - for value in EXCITATION_TEST_VALUES: - self._lewis.backdoor_command( - ["device", "set_sensor_property_backdoor", str(chan), "excitation", str(value)] - ) - self.ca.assert_that_pv_is("CHANNELS:T{}:EXCITATION".format(chan), value) + for chan, value in itertools.product(VALID_TEMPERATURE_SENSORS, EXCITATION_TEST_VALUES): + self._lewis.backdoor_command(["device", "set_sensor_property_backdoor", str(chan), "excitation", str(value)]) + self.ca.assert_that_pv_is("CHANNELS:T{}:EXCITATION".format(chan), value) @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_channel_pause_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written(self): - for chan in VALID_TEMPERATURE_SENSORS: - for value in TIME_DELAY_TEST_VALUES: - self._lewis.backdoor_command( - ["device", "set_sensor_property_backdoor", str(chan), "pause", str(value)] - ) - self.ca.assert_that_pv_is("CHANNELS:T{}:PAUSE".format(chan), value) + for chan, value in itertools.product(VALID_TEMPERATURE_SENSORS, TIME_DELAY_TEST_VALUES): + self._lewis.backdoor_command(["device", "set_sensor_property_backdoor", str(chan), "pause", str(value)]) + self.ca.assert_that_pv_is("CHANNELS:T{}:PAUSE".format(chan), value) @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") def test_WHEN_channel_dwell_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written(self): - for chan in VALID_TEMPERATURE_SENSORS: - for value in TIME_DELAY_TEST_VALUES: - self._lewis.backdoor_command( - ["device", "set_sensor_property_backdoor", str(chan), "dwell", str(value)] - ) - self.ca.assert_that_pv_is("CHANNELS:T{}:DWELL".format(chan), value) + for chan, value in itertools.product(VALID_TEMPERATURE_SENSORS, TIME_DELAY_TEST_VALUES): + self._lewis.backdoor_command(["device", "set_sensor_property_backdoor", str(chan), "dwell", str(value)]) + self.ca.assert_that_pv_is("CHANNELS:T{}:DWELL".format(chan), value) @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") - def test_WHEN_heater_heater_current_is_set_via_backdoor_THEN_pv_updates_with_new_value(self): - for curr in HEATER_RANGE_TEST_VALUES: - self._lewis.backdoor_set_on_device("heater_current", curr) - self.ca.assert_that_pv_is_number("HEATER:CURR", curr) + def test_WHEN_heater_resistance_is_changed_THEN_heater_heater_resistance_pv_updates(self): + for heater_resistance in RESISTANCE_TEST_VALUES: + self._lewis.backdoor_set_on_device("heater_resistance", heater_resistance) + self.ca.assert_that_pv_is_number("HEATER:RES", heater_resistance) @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") - def test_WHEN_heater_power_and_range_are_changed_THEN_heater_percent_power_is_calculated_correctly(self): - for heater_range in HEATER_RANGE_TEST_VALUES: - for current in HEATER_RANGE_TEST_VALUES: - self._lewis.backdoor_set_on_device("heater_current", current) - self._lewis.backdoor_set_on_device("heater_range", heater_range) + def test_WHEN_heater_resistance_and_power_are_changed_THEN_heater_current_is_calculated_correctly(self): + for res, power in itertools.product(RESISTANCE_TEST_VALUES, POWER_TEST_VALUES): + self._lewis.backdoor_set_on_device("heater_resistance", res) + self._lewis.backdoor_set_on_device("heater_power", power) + + self.ca.assert_that_pv_is_number("HEATER:CURR", (power/res)**0.5, tolerance=0.01) # Ohm's law P = RI^2 + + @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + def test_WHEN_heater_current_and_range_are_changed_THEN_heater_percent_power_is_calculated_correctly(self): + + for rang, res, power in itertools.product(HEATER_RANGE_TEST_VALUES, RESISTANCE_TEST_VALUES, POWER_TEST_VALUES): + self._lewis.backdoor_set_on_device("heater_resistance", res) + self._lewis.backdoor_set_on_device("heater_power", power) + self._lewis.backdoor_set_on_device("heater_range", rang) + + assert rang != 0, "Heater range of zero will cause a zero division error!" - assert heater_range != 0, "Heater range of zero will cause a zero division error!" - self.ca.assert_that_pv_is_number("HEATER:PERCENT", 100*current/heater_range, tolerance=0.05) + self.ca.assert_that_pv_is_number("HEATER:PERCENT", 100*((power/res)**0.5)/rang, tolerance=0.05) From 44ef327161e6f03bab7c0a3a4329c833c2362e23 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 8 Feb 2018 09:49:36 +0000 Subject: [PATCH 47/72] Forgotten commit --- tests/triton.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/triton.py b/tests/triton.py index dfb17b1..1ec3463 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -20,7 +20,7 @@ TIME_DELAY_TEST_VALUES = RESISTANCE_TEST_VALUES VALID_TEMPERATURE_SENSORS = [i for i in range(0, 6)] -VALID_PRESSURE_SENSORS = [i for i in range(1, 6)] +VALID_PRESSURE_SENSORS = [1, 2, 3, 5] class TritonTests(unittest.TestCase): From b547d9faf060b850ac58035ce821dc35bba70898 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 16 Feb 2018 13:31:15 +0000 Subject: [PATCH 48/72] Convery triton tests to new style --- tests/triton.py | 66 +++++++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/tests/triton.py b/tests/triton.py index 1ec3463..06d593a 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -1,14 +1,14 @@ from __future__ import division import unittest -from unittest import skipIf import itertools from utils.channel_access import ChannelAccess -from utils.ioc_launcher import IOCRegister -from utils.testing import get_running_lewis_and_ioc +from utils.ioc_launcher import get_default_ioc_dir +from utils.test_modes import TestModes +from utils.testing import get_running_lewis_and_ioc, skip_if_recsim -DEVICE_PREFIX = "TRITON_01" +DEVICE_PREFIX = "TRITON_02" PID_TEST_VALUES = 0, 10**-5, 123.45, 10**5 TEMPERATURE_TEST_VALUES = 0, 10**-5, 5.4321, 250 @@ -23,12 +23,24 @@ VALID_PRESSURE_SENSORS = [1, 2, 3, 5] +IOCS = [ + { + "name": DEVICE_PREFIX, + "directory": get_default_ioc_dir("TRITON", 2), + "emulator": "triton", + }, +] + + +TEST_MODES = [TestModes.RECSIM, TestModes.DEVSIM] + + class TritonTests(unittest.TestCase): """ Tests for the Triton IOC. """ def setUp(self): - self._lewis, self._ioc = get_running_lewis_and_ioc("triton") + self._lewis, self._ioc = get_running_lewis_and_ioc("triton", DEVICE_PREFIX) self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX) def test_WHEN_device_is_started_THEN_it_is_not_disabled(self): @@ -54,33 +66,33 @@ def test_WHEN_heater_range_is_set_THEN_readback_updates(self): for value in HEATER_RANGE_TEST_VALUES: self.ca.assert_setting_setpoint_sets_readback(value, "HEATER:RANGE") - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_heater_power_is_set_via_backdoor_THEN_pv_has_the_value_just_set(self): for value in HEATER_RANGE_TEST_VALUES: self._lewis.backdoor_set_on_device("heater_power", value) self.ca.assert_that_pv_is("HEATER:POWER", value) - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_closed_loop_mode_is_set_via_backdoor_THEN_the_closed_loop_pv_updates_with_value_just_set(self): for value in [False, True, False]: # Need to check both transitions work properly self._lewis.backdoor_set_on_device("closed_loop", value) self.ca.assert_that_pv_is("CLOSEDLOOP", "On" if value else "Off") - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_valve_state_is_set_via_backdoor_THEN_valve_state_pvs_update_with_value_just_set(self): for valve in range(1, 11): for valve_state in [False, True, False]: self._lewis.backdoor_command(["device", "set_valve_state_backdoor", str(valve), str(valve_state)]) self.ca.assert_that_pv_is("VALVES:V{}:STATE".format(valve), "OPEN" if valve_state else "CLOSED") - @skipIf(IOCRegister.uses_rec_sim, "Behaviour too complex for recsim") + @skip_if_recsim("Behaviour too complex for recsim") def test_WHEN_channels_are_enabled_and_disabled_via_pv_THEN_the_readback_pv_updates_with_value_just_set(self): for chan in VALID_TEMPERATURE_SENSORS: for enabled in [False, True, False]: # Need to check both transitions work properly self.ca.assert_setting_setpoint_sets_readback( "ON" if enabled else "OFF", "CHANNELS:T{}:STATE".format(chan)) - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_a_short_status_is_set_on_device_THEN_displayed_status_is_identical(self): # Status message that could be contained in an EPICS string type short_status = "Device status" @@ -95,7 +107,7 @@ def test_WHEN_a_short_status_is_set_on_device_THEN_displayed_status_is_identical self._lewis.backdoor_set_on_device("status", status) self.ca.assert_that_pv_is("STATUS", status) - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_long_status_is_set_on_device_THEN_displayed_status_truncated_but_displays_at_least_500_chars(self): # Somewhat arbitrary, but decide on a minimum number of characters that should be displayed in a @@ -113,7 +125,7 @@ def test_WHEN_long_status_is_set_on_device_THEN_displayed_status_truncated_but_d self.ca.assert_pv_value_causes_func_to_return_true( "STATUS", lambda val: long_status.startswith(val) and len(val) >= minimum_characters_in_pv) - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_automation_is_set_on_device_THEN_displayed_automation_is_identical(self): automations = [ "Warming up to 200K", @@ -127,37 +139,37 @@ def test_WHEN_automation_is_set_on_device_THEN_displayed_automation_is_identical def _set_temp_via_backdoor(self, channel, temp): self._lewis.backdoor_command(["device", "set_temperature_backdoor", "'{}'".format(channel), "{}".format(temp)]) - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_stil_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: self._set_temp_via_backdoor("STIL", temp) self.ca.assert_that_pv_is("STIL:TEMP", temp) - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_mc_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: self._set_temp_via_backdoor("MC", temp) self.ca.assert_that_pv_is("MC:TEMP", temp) - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_sorb_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: self._set_temp_via_backdoor("SORB", temp) self.ca.assert_that_pv_is("SORB:TEMP", temp) - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_4KHX_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: self._set_temp_via_backdoor("PT2", temp) self.ca.assert_that_pv_is("4KHX:TEMP", temp) - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_jthx_temp_is_set_via_backdoor_THEN_pv_updates(self): for temp in TEMPERATURE_TEST_VALUES: self._set_temp_via_backdoor("PT1", temp) self.ca.assert_that_pv_is("JTHX:TEMP", temp) - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_pressure_is_set_via_backdoor_THEN_pressure_pv_updates(self): for sensor, pressure in itertools.product(VALID_PRESSURE_SENSORS, PRESSURE_TEST_VALUES): self._lewis.backdoor_command(["device", "set_pressure_backdoor", str(sensor), str(pressure)]) @@ -167,49 +179,49 @@ def test_WHEN_closed_loop_is_set_via_pv_THEN_readback_updates(self): for state in [False, True, False]: self.ca.assert_setting_setpoint_sets_readback("On" if state else "Off", "CLOSEDLOOP") - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_read_mc_id_is_issued_via_arbitrary_command_THEN_response_is_in_format_device_uses(self): self.ca.set_pv_value("ARBITRARY:SP", "READ:SYS:DR:CHAN:MC") self.ca.assert_pv_value_causes_func_to_return_true("ARBITRARY", lambda val: val.startswith("STAT:SYS:DR:CHAN:MC:")) - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_channel_temperature_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written(self): for chan, value in itertools.product(VALID_TEMPERATURE_SENSORS, TEMPERATURE_TEST_VALUES): self._lewis.backdoor_command(["device", "set_sensor_property_backdoor", str(chan), "temperature", str(value)]) self.ca.assert_that_pv_is("CHANNELS:T{}:TEMP".format(chan), value) - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_channel_resistance_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written(self): for chan, value in itertools.product(VALID_TEMPERATURE_SENSORS, RESISTANCE_TEST_VALUES): self._lewis.backdoor_command(["device", "set_sensor_property_backdoor", str(chan), "resistance", str(value)]) self.ca.assert_that_pv_is("CHANNELS:T{}:RES".format(chan), value) - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_channel_excitation_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written(self): for chan, value in itertools.product(VALID_TEMPERATURE_SENSORS, EXCITATION_TEST_VALUES): self._lewis.backdoor_command(["device", "set_sensor_property_backdoor", str(chan), "excitation", str(value)]) self.ca.assert_that_pv_is("CHANNELS:T{}:EXCITATION".format(chan), value) - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_channel_pause_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written(self): for chan, value in itertools.product(VALID_TEMPERATURE_SENSORS, TIME_DELAY_TEST_VALUES): self._lewis.backdoor_command(["device", "set_sensor_property_backdoor", str(chan), "pause", str(value)]) self.ca.assert_that_pv_is("CHANNELS:T{}:PAUSE".format(chan), value) - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_channel_dwell_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written(self): for chan, value in itertools.product(VALID_TEMPERATURE_SENSORS, TIME_DELAY_TEST_VALUES): self._lewis.backdoor_command(["device", "set_sensor_property_backdoor", str(chan), "dwell", str(value)]) self.ca.assert_that_pv_is("CHANNELS:T{}:DWELL".format(chan), value) - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_heater_resistance_is_changed_THEN_heater_heater_resistance_pv_updates(self): for heater_resistance in RESISTANCE_TEST_VALUES: self._lewis.backdoor_set_on_device("heater_resistance", heater_resistance) self.ca.assert_that_pv_is_number("HEATER:RES", heater_resistance) - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_heater_resistance_and_power_are_changed_THEN_heater_current_is_calculated_correctly(self): for res, power in itertools.product(RESISTANCE_TEST_VALUES, POWER_TEST_VALUES): self._lewis.backdoor_set_on_device("heater_resistance", res) @@ -217,7 +229,7 @@ def test_WHEN_heater_resistance_and_power_are_changed_THEN_heater_current_is_cal self.ca.assert_that_pv_is_number("HEATER:CURR", (power/res)**0.5, tolerance=0.01) # Ohm's law P = RI^2 - @skipIf(IOCRegister.uses_rec_sim, "Lewis backdoor not available in recsim") + @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_heater_current_and_range_are_changed_THEN_heater_percent_power_is_calculated_correctly(self): for rang, res, power in itertools.product(HEATER_RANGE_TEST_VALUES, RESISTANCE_TEST_VALUES, POWER_TEST_VALUES): From e9c6ffccdd71ef43c6560be9c444378bf84ea6a8 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 16 Feb 2018 13:45:17 +0000 Subject: [PATCH 49/72] change tests back to run on ioc 1 --- tests/triton.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/triton.py b/tests/triton.py index 06d593a..5fb88b2 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -8,7 +8,7 @@ from utils.test_modes import TestModes from utils.testing import get_running_lewis_and_ioc, skip_if_recsim -DEVICE_PREFIX = "TRITON_02" +DEVICE_PREFIX = "TRITON_01" PID_TEST_VALUES = 0, 10**-5, 123.45, 10**5 TEMPERATURE_TEST_VALUES = 0, 10**-5, 5.4321, 250 @@ -26,7 +26,7 @@ IOCS = [ { "name": DEVICE_PREFIX, - "directory": get_default_ioc_dir("TRITON", 2), + "directory": get_default_ioc_dir("TRITON"), "emulator": "triton", }, ] From 01f3f41997bb2506c0de8655f6d8e0b643832a23 Mon Sep 17 00:00:00 2001 From: David Keymer Date: Fri, 23 Mar 2018 17:23:22 +0000 Subject: [PATCH 50/72] Upgrade Lewis --- lewis_emulators/lewis_versions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/lewis_versions.py b/lewis_emulators/lewis_versions.py index 79a658b..4d572c5 100644 --- a/lewis_emulators/lewis_versions.py +++ b/lewis_emulators/lewis_versions.py @@ -1,2 +1,2 @@ -LEWIS_1_1_1 = "1.1.1" -LEWIS_LATEST = LEWIS_1_1_1 \ No newline at end of file +LEWIS_1_2_0 = "1.2.0" +LEWIS_LATEST = LEWIS_1_2_0 \ No newline at end of file From 1ad28b310f63ba2451911d37a6421b221ee6d7cd Mon Sep 17 00:00:00 2001 From: esouthren Date: Tue, 3 Apr 2018 16:34:00 +0100 Subject: [PATCH 51/72] Update lewis_versions.py Added new line --- lewis_emulators/lewis_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/lewis_versions.py b/lewis_emulators/lewis_versions.py index 4d572c5..beae6ab 100644 --- a/lewis_emulators/lewis_versions.py +++ b/lewis_emulators/lewis_versions.py @@ -1,2 +1,2 @@ LEWIS_1_2_0 = "1.2.0" -LEWIS_LATEST = LEWIS_1_2_0 \ No newline at end of file +LEWIS_LATEST = LEWIS_1_2_0 From 37e4fec59eda25ef9be4f440dfb570c1239cf312 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 6 Apr 2018 15:16:59 +0100 Subject: [PATCH 52/72] Remove tests relating to valve statuses --- tests/triton.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/triton.py b/tests/triton.py index 5fb88b2..8a19fca 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -78,13 +78,6 @@ def test_WHEN_closed_loop_mode_is_set_via_backdoor_THEN_the_closed_loop_pv_updat self._lewis.backdoor_set_on_device("closed_loop", value) self.ca.assert_that_pv_is("CLOSEDLOOP", "On" if value else "Off") - @skip_if_recsim("Lewis backdoor not available in recsim") - def test_WHEN_valve_state_is_set_via_backdoor_THEN_valve_state_pvs_update_with_value_just_set(self): - for valve in range(1, 11): - for valve_state in [False, True, False]: - self._lewis.backdoor_command(["device", "set_valve_state_backdoor", str(valve), str(valve_state)]) - self.ca.assert_that_pv_is("VALVES:V{}:STATE".format(valve), "OPEN" if valve_state else "CLOSED") - @skip_if_recsim("Behaviour too complex for recsim") def test_WHEN_channels_are_enabled_and_disabled_via_pv_THEN_the_readback_pv_updates_with_value_just_set(self): for chan in VALID_TEMPERATURE_SENSORS: From a46b5b7fc727be903865c4841e3645656e878557 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 6 Apr 2018 15:19:14 +0100 Subject: [PATCH 53/72] Remove valve statuses --- lewis_emulators/triton/device.py | 29 ------------------- .../triton/interfaces/stream_interface.py | 18 ------------ 2 files changed, 47 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 39a2d8a..c35b943 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -6,15 +6,6 @@ HEATER_NAME = "H1" -class ValveStates(object): - """ - Enum representing the possible states of a valve. - """ - OPEN = 0 - CLOSED = 1 - NOT_FOUND = 2 - - class TemperatureStage(object): """ Class representing a temperature stage. @@ -43,16 +34,6 @@ def __init__(self): self.pressure = 0 -class Valve(object): - """ - Class to represent a valve. - - Having this as a class makes it more extensible in future, as the triton driver is still in flux. - """ - def __init__(self): - self.open = False - - class SimulatedTriton(StateMachineDevice): def _initialize_data(self): @@ -72,7 +53,6 @@ def _initialize_data(self): self.status = "This is a device status message." self.automation = "This is the automation status" - self.valves = {"V{}".format(i): Valve() for i in range(1, 11)} self.pressure_sensors = {"P{}".format(i): PressureSensor() for i in range(1, 6)} self.temperature_stages = { @@ -98,9 +78,6 @@ def find_temperature_channel(self, name): def set_temperature_backdoor(self, stage_name, new_temp): self.temperature_stages[self.find_temperature_channel(stage_name)].temperature = new_temp - def set_valve_state_backdoor(self, valve, newstate): - self.valves["V{}".format(valve)].open = bool(newstate) - def set_pressure_backdoor(self, sensor, newpressure): self.pressure_sensors["P{}".format(sensor)].pressure = float(newpressure) @@ -154,12 +131,6 @@ def get_heater_range(self): def set_heater_range(self, value): self.heater_range = value - def get_valve_state(self, valve): - try: - return ValveStates.OPEN if self.valves[valve].open else ValveStates.CLOSED - except KeyError: - return ValveStates.NOT_FOUND - def is_channel_enabled(self, chan): try: return self.temperature_stages[chan].enabled diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index ddfb1c6..1949da2 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -55,9 +55,6 @@ class TritonStreamInterface(StreamInterface): CmdBuilder("get_closed_loop_mode").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:MODE").eos().build(), CmdBuilder("set_closed_loop_mode").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:MODE:").any().eos().build(), - # Valve state - CmdBuilder("get_valve_state").escape("READ:DEV:").arg("V[0-9]+").escape(":VALV:SIG:STATE").eos().build(), - # Channel enablement CmdBuilder("get_channel_enabled").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:MEAS:ENAB").eos().build(), CmdBuilder("set_channel_enabled").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:MEAS:ENAB:").any().eos().build(), @@ -168,21 +165,6 @@ def set_closed_loop_mode(self, chan, mode): self.device.set_closed_loop_mode(mode == "ON") return "STAT:SET:DEV:{}:TEMP:LOOP:MODE:{}:VALID".format(chan, mode) - def get_valve_state(self, valve): - - state = self.device.get_valve_state(valve) - - if state == ValveStates.CLOSED: - response = "CLOSE" - elif state == ValveStates.OPEN: - response = "OPEN" - elif state == ValveStates.NOT_FOUND: - response = "NOT_FOUND" - else: - raise ValueError("Invalid valve state: {}".format(state)) - - return "STAT:DEV:{}:VALV:SIG:STATE:{}".format(valve, response) - def get_channel_enabled(self, channel): return "STAT:DEV:{}:TEMP:MEAS:ENAB:{}"\ .format(channel, "ON" if self.device.is_channel_enabled(channel) else "OFF") From ff1e85c4552d2d8337e3cb34630022a6df8baba3 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 6 Apr 2018 16:09:13 +0100 Subject: [PATCH 54/72] Adjust valid channels range --- tests/triton.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/triton.py b/tests/triton.py index 8a19fca..dc23313 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -19,7 +19,7 @@ EXCITATION_TEST_VALUES = PID_TEST_VALUES TIME_DELAY_TEST_VALUES = RESISTANCE_TEST_VALUES -VALID_TEMPERATURE_SENSORS = [i for i in range(0, 6)] +VALID_TEMPERATURE_SENSORS = [i for i in range(1, 7)] VALID_PRESSURE_SENSORS = [1, 2, 3, 5] From 2d2c02402267fa8c09012283d9937ec8e0e454b3 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 6 Apr 2018 16:09:40 +0100 Subject: [PATCH 55/72] Remove invalid import. No longer emulate off-by-one error --- lewis_emulators/triton/device.py | 6 +++--- lewis_emulators/triton/interfaces/stream_interface.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index c35b943..d32149b 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -82,9 +82,9 @@ def set_pressure_backdoor(self, sensor, newpressure): self.pressure_sensors["P{}".format(sensor)].pressure = float(newpressure) def set_sensor_property_backdoor(self, sensor, property, value): - # The sensor + 1 is due to an indexing error in the Oxford Instruments firmware. - # We are emulating this off-by-one error. - setattr(self.temperature_stages["T{}".format(sensor+1)], property, value) + # In older versions of the software, there was an off-by-one error here in the OI software. + # This has now been fixed by O.I. so no longer need to adjust by one here. + setattr(self.temperature_stages["T{}".format(sensor)], property, value) def _get_state_handlers(self): return {'default': DefaultState()} diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 1949da2..bbbd766 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -4,7 +4,6 @@ from lewis.core.logging import has_log from lewis_emulators.utils.command_builder import CmdBuilder from lewis_emulators.triton.device import HEATER_NAME -from lewis_emulators.triton.device import ValveStates @has_log From 5d63938b104dce3d45aa7affde33fa55f5f51390 Mon Sep 17 00:00:00 2001 From: Rory-Potter-Tessella Date: Fri, 1 Jun 2018 11:11:57 +0100 Subject: [PATCH 56/72] Changed module name and updated emulator to pass temp test. --- lewis_emulators/__init__.py | 1 - 1 file changed, 1 deletion(-) delete mode 100644 lewis_emulators/__init__.py diff --git a/lewis_emulators/__init__.py b/lewis_emulators/__init__.py deleted file mode 100644 index c396168..0000000 --- a/lewis_emulators/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import absolute_import From f46b96ef55e3a2979a9bc74286cc46527414309a Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 15 Jun 2018 13:42:05 +0100 Subject: [PATCH 57/72] Re-add lost init file, with comment saying do not delete. --- lewis_emulators/__init__.py | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 lewis_emulators/__init__.py diff --git a/lewis_emulators/__init__.py b/lewis_emulators/__init__.py new file mode 100644 index 0000000..ad92558 --- /dev/null +++ b/lewis_emulators/__init__.py @@ -0,0 +1,2 @@ +# DO NOT DELETE THIS FILE - LEWIS FRAMEWORK REQUIRES THE DIRECTORY TO BE IMPORTABLE +from __future__ import absolute_import From 5d8b3e37100a2e05566d1ffe7714cd4861daf599 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Jun 2018 14:25:37 +0100 Subject: [PATCH 58/72] Convert docs to new style. Make assertion names consistent. --- tests/triton.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/triton.py b/tests/triton.py index dc23313..5fac7e2 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -115,7 +115,7 @@ def test_WHEN_long_status_is_set_on_device_THEN_displayed_status_truncated_but_d # Allow truncation for long status, but it should still display as many characters as possible self._lewis.backdoor_set_on_device("status", long_status) - self.ca.assert_pv_value_causes_func_to_return_true( + self.ca.assert_that_pv_value_causes_func_to_return_true( "STATUS", lambda val: long_status.startswith(val) and len(val) >= minimum_characters_in_pv) @skip_if_recsim("Lewis backdoor not available in recsim") @@ -175,8 +175,8 @@ def test_WHEN_closed_loop_is_set_via_pv_THEN_readback_updates(self): @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_read_mc_id_is_issued_via_arbitrary_command_THEN_response_is_in_format_device_uses(self): self.ca.set_pv_value("ARBITRARY:SP", "READ:SYS:DR:CHAN:MC") - self.ca.assert_pv_value_causes_func_to_return_true("ARBITRARY", - lambda val: val.startswith("STAT:SYS:DR:CHAN:MC:")) + self.ca.assert_that_pv_value_causes_func_to_return_true("ARBITRARY", + lambda val: val.startswith("STAT:SYS:DR:CHAN:MC:")) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_channel_temperature_is_set_via_backdoor_THEN_the_pvs_update_with_values_just_written(self): From 3601420283820710753b70230d9469df2fadb27c Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 9 Oct 2018 15:52:54 +0100 Subject: [PATCH 59/72] Refactor emulator. - Move all formatting logic to stream interface - Make internals more generic - Update for commands which we are now using from IOC --- lewis_emulators/triton/interfaces/stream_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index bbbd766..faf4c37 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -1,6 +1,6 @@ from datetime import datetime -from lewis.adapters.stream import StreamInterface, Cmd +from lewis.adapters.stream import StreamInterface from lewis.core.logging import has_log from lewis_emulators.utils.command_builder import CmdBuilder from lewis_emulators.triton.device import HEATER_NAME From 852eb547dcd58217ee9ebd4f6a1235f8b5dfc0ba Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Mar 2019 22:52:51 +0000 Subject: [PATCH 60/72] Add tests to ensure closed loop mode is set on --- tests/triton.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/triton.py b/tests/triton.py index 5fac7e2..f9a35f9 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -28,6 +28,10 @@ "name": DEVICE_PREFIX, "directory": get_default_ioc_dir("TRITON"), "emulator": "triton", + "macros": { + "USE_RAMP_FILE": 0, + "RAMP_FILE_NAME": "Default.txt", + } }, ] @@ -62,6 +66,23 @@ def test_WHEN_temperature_setpoint_is_set_THEN_readback_updates(self): for value in TEMPERATURE_TEST_VALUES: self.ca.assert_setting_setpoint_sets_readback(value, set_point_pv="TEMP:SP", readback_pv="TEMP:SP:RBV") + def test_WHEN_temperature_setpoint_is_set_THEN_closed_loop_turned_on_automatically(self): + for value in TEMPERATURE_TEST_VALUES: + self.ca.set_pv_value("CLOSEDLOOP:SP", "Off") + self.ca.assert_that_pv_is("CLOSEDLOOP", "Off") + self.ca.assert_setting_setpoint_sets_readback(value, set_point_pv="TEMP:SP", readback_pv="TEMP:SP:RBV") + self.ca.assert_that_pv_is("CLOSEDLOOP", "On") + + def test_GIVEN_closed_loop_already_on_WHEN_temperature_setpoint_is_set_THEN_closed_loop_setpoint_not_reprocessed(self): + for value in TEMPERATURE_TEST_VALUES: + self.ca.set_pv_value("CLOSEDLOOP:SP", "On") + self.ca.assert_that_pv_is("CLOSEDLOOP", "On") + timestamp_before = self.ca.get_pv_value("CLOSEDLOOP:SP.TSEL") + self.ca.assert_setting_setpoint_sets_readback(value, set_point_pv="TEMP:SP", readback_pv="TEMP:SP:RBV") + self.ca.assert_that_pv_is("CLOSEDLOOP", "On") + self.ca.assert_that_pv_is("CLOSEDLOOP:SP.TSEL", timestamp_before) + self.ca.assert_that_pv_value_is_unchanged("CLOSEDLOOP:SP.TSEL", wait=5) + def test_WHEN_heater_range_is_set_THEN_readback_updates(self): for value in HEATER_RANGE_TEST_VALUES: self.ca.assert_setting_setpoint_sets_readback(value, "HEATER:RANGE") @@ -71,6 +92,7 @@ def test_WHEN_heater_power_is_set_via_backdoor_THEN_pv_has_the_value_just_set(se for value in HEATER_RANGE_TEST_VALUES: self._lewis.backdoor_set_on_device("heater_power", value) self.ca.assert_that_pv_is("HEATER:POWER", value) + self.ca.assert_that_pv_alarm_is("HEATER:POWER", self.ca.Alarms.NONE) @skip_if_recsim("Lewis backdoor not available in recsim") def test_WHEN_closed_loop_mode_is_set_via_backdoor_THEN_the_closed_loop_pv_updates_with_value_just_set(self): From 7bdefb00d23da963012f4e2ec8a9cd9e8d868608 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 17 Apr 2019 12:35:53 +0100 Subject: [PATCH 61/72] Skip test in recsim --- tests/triton.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/triton.py b/tests/triton.py index f9a35f9..af5ec50 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -66,6 +66,7 @@ def test_WHEN_temperature_setpoint_is_set_THEN_readback_updates(self): for value in TEMPERATURE_TEST_VALUES: self.ca.assert_setting_setpoint_sets_readback(value, set_point_pv="TEMP:SP", readback_pv="TEMP:SP:RBV") + @skip_if_recsim("This is implemented at the protocol level, so does not work in recsim") def test_WHEN_temperature_setpoint_is_set_THEN_closed_loop_turned_on_automatically(self): for value in TEMPERATURE_TEST_VALUES: self.ca.set_pv_value("CLOSEDLOOP:SP", "Off") From ac8b998d8d4336bff9d2cc17221ad64553f08029 Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Wed, 25 Sep 2019 16:58:41 +0100 Subject: [PATCH 62/72] Updated lewis version --- lewis_emulators/lewis_versions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/lewis_versions.py b/lewis_emulators/lewis_versions.py index beae6ab..a805cfb 100644 --- a/lewis_emulators/lewis_versions.py +++ b/lewis_emulators/lewis_versions.py @@ -1,2 +1,2 @@ -LEWIS_1_2_0 = "1.2.0" -LEWIS_LATEST = LEWIS_1_2_0 +LEWIS_1_2_1 = "1.2.1" +LEWIS_LATEST = LEWIS_1_2_1 From ba310cda29e3cbe79ceada7385de415d5d3f6592 Mon Sep 17 00:00:00 2001 From: ChrisColeExpControl Date: Tue, 15 Oct 2019 09:43:23 +0100 Subject: [PATCH 63/72] renamed to cryogenic_sms/CRYOSMS --- lewis_emulators/lewis_versions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/lewis_versions.py b/lewis_emulators/lewis_versions.py index beae6ab..fc253d0 100644 --- a/lewis_emulators/lewis_versions.py +++ b/lewis_emulators/lewis_versions.py @@ -1,2 +1,3 @@ LEWIS_1_2_0 = "1.2.0" -LEWIS_LATEST = LEWIS_1_2_0 +LEWIS_1_2_1 = "1.2.1" +LEWIS_LATEST = LEWIS_1_2_1 From cd122cbe36903d645f3ca9c736ee034e531752f7 Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Mon, 1 Jun 2020 18:29:20 +0100 Subject: [PATCH 64/72] Updated lewis version to 1.2.2 --- lewis_emulators/lewis_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/lewis_versions.py b/lewis_emulators/lewis_versions.py index a805cfb..5849f8b 100644 --- a/lewis_emulators/lewis_versions.py +++ b/lewis_emulators/lewis_versions.py @@ -1,2 +1,2 @@ -LEWIS_1_2_1 = "1.2.1" +LEWIS_1_2_1 = "1.2.2" LEWIS_LATEST = LEWIS_1_2_1 From f66709e6775c9b64e7967cd87f2fabca9d83dc11 Mon Sep 17 00:00:00 2001 From: aaronlong Date: Wed, 16 Sep 2020 13:28:31 +0100 Subject: [PATCH 65/72] Updated a number of imports, python3 syntax changes and minor PEP8 violations --- lewis_emulators/triton/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index d32149b..016e2a3 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from states import DefaultState +from .states import DefaultState from lewis.devices import StateMachineDevice From 9112de0780845cedc14dc56f1c5bd40f62696f48 Mon Sep 17 00:00:00 2001 From: aaronlong Date: Wed, 16 Sep 2020 14:48:32 +0100 Subject: [PATCH 66/72] Fixed variable name for lewis version number --- lewis_emulators/lewis_versions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/lewis_versions.py b/lewis_emulators/lewis_versions.py index 5849f8b..5ae639f 100644 --- a/lewis_emulators/lewis_versions.py +++ b/lewis_emulators/lewis_versions.py @@ -1,2 +1,2 @@ -LEWIS_1_2_1 = "1.2.2" -LEWIS_LATEST = LEWIS_1_2_1 +LEWIS_1_2_2 = "1.2.2" +LEWIS_LATEST = LEWIS_1_2_2 From a089fc9088d2cd921aaacf4682559d9a425d67a4 Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Tue, 10 Nov 2020 20:29:26 +0000 Subject: [PATCH 67/72] Updated to use latest lewis version and convert strings correctly --- lewis_emulators/lewis_versions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/lewis_versions.py b/lewis_emulators/lewis_versions.py index 5ae639f..99831a8 100644 --- a/lewis_emulators/lewis_versions.py +++ b/lewis_emulators/lewis_versions.py @@ -1,2 +1,2 @@ -LEWIS_1_2_2 = "1.2.2" -LEWIS_LATEST = LEWIS_1_2_2 +LEWIS_2_0_0 = "2.0.0" +LEWIS_LATEST = LEWIS_2_0_0 From 36ec9c7493ac7a66bfc1055e29e685fc4abb26e5 Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Mon, 30 Nov 2020 14:18:57 +0000 Subject: [PATCH 68/72] Update lewis version --- lewis_emulators/lewis_versions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/lewis_versions.py b/lewis_emulators/lewis_versions.py index 99831a8..0f6c45a 100644 --- a/lewis_emulators/lewis_versions.py +++ b/lewis_emulators/lewis_versions.py @@ -1,2 +1,2 @@ -LEWIS_2_0_0 = "2.0.0" -LEWIS_LATEST = LEWIS_2_0_0 +LEWIS_1_3_0 = "1.3.0" +LEWIS_LATEST = LEWIS_1_3_0 From f67b2ef9297a88e8d696e8b3825356a1be50e535 Mon Sep 17 00:00:00 2001 From: Jack Harper Date: Fri, 11 Dec 2020 10:30:23 +0000 Subject: [PATCH 69/72] removing __future__ imports --- tests/triton.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/triton.py b/tests/triton.py index af5ec50..1b142c5 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -1,5 +1,3 @@ -from __future__ import division - import unittest import itertools From c3cd652fb1ad788f0ff714d26898eff55c54246b Mon Sep 17 00:00:00 2001 From: JamesKingWork Date: Thu, 18 Mar 2021 10:47:18 +0000 Subject: [PATCH 70/72] Configure poll rate for tests --- tests/triton.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/triton.py b/tests/triton.py index 1b142c5..c1be736 100644 --- a/tests/triton.py +++ b/tests/triton.py @@ -29,6 +29,8 @@ "macros": { "USE_RAMP_FILE": 0, "RAMP_FILE_NAME": "Default.txt", + "POLL_RATE": "1 second", + "CHANNEL_POLL_RATE": "2 second" } }, ] From 01f5adef4fb607ecd97d8ac35316665f6545d65e Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Thu, 10 Jun 2021 11:38:28 +0100 Subject: [PATCH 71/72] Moved utils into lewis itself --- lewis_emulators/triton/interfaces/stream_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index faf4c37..2bf2ded 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -2,7 +2,7 @@ from lewis.adapters.stream import StreamInterface from lewis.core.logging import has_log -from lewis_emulators.utils.command_builder import CmdBuilder +from lewis.utils.command_builder import CmdBuilder from lewis_emulators.triton.device import HEATER_NAME From 60450345f4f89711cafa4ffe42bae04a90127257 Mon Sep 17 00:00:00 2001 From: aaronlong Date: Fri, 6 May 2022 13:51:11 +0100 Subject: [PATCH 72/72] moved system tests into support module --- .gitignore | 4 ++++ Makefile | 3 +++ system_tests/__init__.py | 0 .../lewis_emulators}/__init__.py | 0 .../lewis_emulators}/lewis_versions.py | 0 .../lewis_emulators}/triton/__init__.py | 0 .../lewis_emulators}/triton/device.py | 0 .../lewis_emulators}/triton/interfaces/__init__.py | 0 .../triton/interfaces/stream_interface.py | 0 .../lewis_emulators}/triton/states.py | 0 system_tests/run_tests.bat | 10 ++++++++++ system_tests/tests/__init__.py | 0 {tests => system_tests/tests}/triton.py | 0 13 files changed, 17 insertions(+) create mode 100644 system_tests/__init__.py rename {lewis_emulators => system_tests/lewis_emulators}/__init__.py (100%) rename {lewis_emulators => system_tests/lewis_emulators}/lewis_versions.py (100%) rename {lewis_emulators => system_tests/lewis_emulators}/triton/__init__.py (100%) rename {lewis_emulators => system_tests/lewis_emulators}/triton/device.py (100%) rename {lewis_emulators => system_tests/lewis_emulators}/triton/interfaces/__init__.py (100%) rename {lewis_emulators => system_tests/lewis_emulators}/triton/interfaces/stream_interface.py (100%) rename {lewis_emulators => system_tests/lewis_emulators}/triton/states.py (100%) create mode 100644 system_tests/run_tests.bat create mode 100644 system_tests/tests/__init__.py rename {tests => system_tests/tests}/triton.py (100%) diff --git a/.gitignore b/.gitignore index ed7e36d..f8a3fdf 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,7 @@ relPaths.sh /doc/ *_info_positions.req *_info_settings.req +__pycache__/ +*.py[cod] +*$py.class +test-reports/ diff --git a/Makefile b/Makefile index bab6eff..9e4fe0e 100644 --- a/Makefile +++ b/Makefile @@ -12,3 +12,6 @@ DIRS += $(wildcard *[Aa]pp) DIRS += $(wildcard ioc[Bb]oot) include $(TOP)/configure/RULES_TOP + +ioctests: + .\system_tests\run_tests.bat diff --git a/system_tests/__init__.py b/system_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lewis_emulators/__init__.py b/system_tests/lewis_emulators/__init__.py similarity index 100% rename from lewis_emulators/__init__.py rename to system_tests/lewis_emulators/__init__.py diff --git a/lewis_emulators/lewis_versions.py b/system_tests/lewis_emulators/lewis_versions.py similarity index 100% rename from lewis_emulators/lewis_versions.py rename to system_tests/lewis_emulators/lewis_versions.py diff --git a/lewis_emulators/triton/__init__.py b/system_tests/lewis_emulators/triton/__init__.py similarity index 100% rename from lewis_emulators/triton/__init__.py rename to system_tests/lewis_emulators/triton/__init__.py diff --git a/lewis_emulators/triton/device.py b/system_tests/lewis_emulators/triton/device.py similarity index 100% rename from lewis_emulators/triton/device.py rename to system_tests/lewis_emulators/triton/device.py diff --git a/lewis_emulators/triton/interfaces/__init__.py b/system_tests/lewis_emulators/triton/interfaces/__init__.py similarity index 100% rename from lewis_emulators/triton/interfaces/__init__.py rename to system_tests/lewis_emulators/triton/interfaces/__init__.py diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/system_tests/lewis_emulators/triton/interfaces/stream_interface.py similarity index 100% rename from lewis_emulators/triton/interfaces/stream_interface.py rename to system_tests/lewis_emulators/triton/interfaces/stream_interface.py diff --git a/lewis_emulators/triton/states.py b/system_tests/lewis_emulators/triton/states.py similarity index 100% rename from lewis_emulators/triton/states.py rename to system_tests/lewis_emulators/triton/states.py diff --git a/system_tests/run_tests.bat b/system_tests/run_tests.bat new file mode 100644 index 0000000..65ab1d6 --- /dev/null +++ b/system_tests/run_tests.bat @@ -0,0 +1,10 @@ +@echo off +setlocal +REM Run this directory's tests using the IOC Testing Framework + +call "%~dp0..\..\..\..\config_env.bat" + +REM Command line arguments always passed to the test script +SET ARGS=--test_and_emulator %~dp0 +call %PYTHON3% -u "%EPICS_KIT_ROOT%\support\IocTestFramework\master\run_tests.py" %ARGS% %* +IF %ERRORLEVEL% NEQ 0 EXIT /b %ERRORLEVEL% diff --git a/system_tests/tests/__init__.py b/system_tests/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/triton.py b/system_tests/tests/triton.py similarity index 100% rename from tests/triton.py rename to system_tests/tests/triton.py