From 564ba56cfa99b83226822b1b31081b7eeef00ddd Mon Sep 17 00:00:00 2001 From: Oliver Silvester Date: Mon, 12 Jun 2023 11:58:06 +0100 Subject: [PATCH 01/12] Add as device --- src/dodal/devices/attenuator/attenuator.py | 136 +++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 src/dodal/devices/attenuator/attenuator.py diff --git a/src/dodal/devices/attenuator/attenuator.py b/src/dodal/devices/attenuator/attenuator.py new file mode 100644 index 0000000000..62297bccb1 --- /dev/null +++ b/src/dodal/devices/attenuator/attenuator.py @@ -0,0 +1,136 @@ +from typing import Optional + +from ophyd import Arming, Component, Device, EpicsSignal, Signal +from ophyd.status import SubscriptionStatus + +from dodal.devices.detector import DetectorParams +from dodal.devices.status import await_value +from dodal.log import LOGGER + + +class Attenuator(Device): + class TransmissionSignal(Signal): + def set(self, value, *, timeout=None, settle_time=None, **kwargs): + return self.parent.set_transmission() + + do_set_transmission: TransmissionSignal = Component(TransmissionSignal) + + # Could make a separate class for these, but that's potentially pointless as this is the only PV for it + calulated_filter_state_0: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B0") + calulated_filter_state_1: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B1") + calulated_filter_state_2: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B2") + calulated_filter_state_3: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B3") + calulated_filter_state_4: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B4") + calulated_filter_state_5: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B5") + calulated_filter_state_6: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B6") + calulated_filter_state_7: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B7") + calulated_filter_state_8: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B8") + calulated_filter_state_9: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B9") + calulated_filter_state_10: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BA") + calulated_filter_state_11: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BB") + calulated_filter_state_12: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BC") + calulated_filter_state_13: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BD") + calulated_filter_state_14: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BE") + calulated_filter_state_15: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BF") + + # Could also make another class for this - but again it's the only PV used + actual_filter_state_1: EpicsSignal = Component(EpicsSignal, ":FILTER1:INLIM") + actual_filter_state_2: EpicsSignal = Component(EpicsSignal, ":FILTER2:INLIM") + actual_filter_state_3: EpicsSignal = Component(EpicsSignal, ":FILTER3:INLIM") + actual_filter_state_4: EpicsSignal = Component(EpicsSignal, ":FILTER4:INLIM") + actual_filter_state_5: EpicsSignal = Component(EpicsSignal, ":FILTER5:INLIM") + actual_filter_state_6: EpicsSignal = Component(EpicsSignal, ":FILTER6:INLIM") + actual_filter_state_7: EpicsSignal = Component(EpicsSignal, ":FILTER7:INLIM") + actual_filter_state_8: EpicsSignal = Component(EpicsSignal, ":FILTER8:INLIM") + actual_filter_state_9: EpicsSignal = Component(EpicsSignal, ":FILTER9:INLIM") + actual_filter_state_10: EpicsSignal = Component(EpicsSignal, ":FILTER10:INLIM") + actual_filter_state_11: EpicsSignal = Component(EpicsSignal, ":FILTER11:INLIM") + actual_filter_state_12: EpicsSignal = Component(EpicsSignal, ":FILTER12:INLIM") + actual_filter_state_13: EpicsSignal = Component(EpicsSignal, ":FILTER13:INLIM") + actual_filter_state_14: EpicsSignal = Component(EpicsSignal, ":FILTER14:INLIM") + actual_filter_state_15: EpicsSignal = Component(EpicsSignal, ":FILTER15:INLIM") + actual_filter_state_16: EpicsSignal = Component(EpicsSignal, ":FILTER16:INLIM") + + desired_transmission: EpicsSignal = Component(EpicsSignal, ":T2A:SETVAL1") + use_current_energy: EpicsSignal = Component( + EpicsSignal, ":E2WL:USECURRENTENERGY.PROC" + ) + change: EpicsSignal = Component(EpicsSignal, ":FANOUT") + actual_transmission: EpicsSignal = Component(EpicsSignal, ":MATCH") + + detector_params: Optional[DetectorParams] = None + + def get_calculated_filter_state_list(self) -> list[EpicsSignal]: + return [ + self.calulated_filter_state_0, + self.calulated_filter_state_1, + self.calulated_filter_state_2, + self.calulated_filter_state_3, + self.calulated_filter_state_4, + self.calulated_filter_state_5, + self.calulated_filter_state_6, + self.calulated_filter_state_7, + self.calulated_filter_state_8, + self.calulated_filter_state_9, + self.calulated_filter_state_10, + self.calulated_filter_state_11, + self.calulated_filter_state_12, + self.calulated_filter_state_13, + self.calulated_filter_state_14, + self.calulated_filter_state_15, + ] + + def get_actual_filter_state_list(self) -> list[EpicsSignal]: + return [ + self.actual_filter_state_1, + self.actual_filter_state_2, + self.actual_filter_state_3, + self.actual_filter_state_4, + self.actual_filter_state_5, + self.actual_filter_state_6, + self.actual_filter_state_7, + self.actual_filter_state_8, + self.actual_filter_state_9, + self.actual_filter_state_10, + self.actual_filter_state_11, + self.actual_filter_state_12, + self.actual_filter_state_13, + self.actual_filter_state_14, + self.actual_filter_state_15, + self.actual_filter_state_16, + ] + + def set_transmission(self, transmission) -> SubscriptionStatus: + """Get desired states and calculated states, return a status which is complete once they are equal""" + # put this in try block? + + LOGGER.info("Using current energy") + self.pv_use_current_energy.put(1) + LOGGER.info(f"Setting desired transmission to {transmission}") + self.pv_desired_transmission.put(transmission) + LOGGER.info("Sending change filter command") + self.pv_change.put(1) + + # Get desired filter positions (16phase) + + desired_states = [] + + # Get the boolean desired state of each of the 16 filters + # TODO: put in a try catch block since the get might timeout + for calculated_state in self.get_calculated_filter_state_list(): + value = int(calculated_state.get(timeout=10)) + desired_states.append(value == 1) + + actual_states = [] + + # Get the boolean actual state of each of the 16 filters + for actual_state in self.get_actual_filter_state_list(): + value = actual_state.get(timeout=10) + actual_states.append(value == "in") + + return await_value(actual_states, desired_states, timeout=30) + + # If all the actual states are equal to the desired state, we can move on + + # and check if atteunator is ready + # Get actual positions (get filter positions() from 16 phase) From d64de38e5d6f392e8a6b377dbad7a895b2e241bd Mon Sep 17 00:00:00 2001 From: Oliver Silvester Date: Mon, 12 Jun 2023 12:03:11 +0100 Subject: [PATCH 02/12] Added filter pv's --- src/dodal/devices/attenuator/filter.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/dodal/devices/attenuator/filter.py diff --git a/src/dodal/devices/attenuator/filter.py b/src/dodal/devices/attenuator/filter.py new file mode 100644 index 0000000000..8002bdaaad --- /dev/null +++ b/src/dodal/devices/attenuator/filter.py @@ -0,0 +1,22 @@ +from ophyd import Component, Device, EpicsSignal, EpicsSignalRO + +from dodal.devices.attenuator.attenuator import Attenuator + + +class AtteunatorFilter(Device): + pv_calculated_state_0: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B0") + pv_calculated_state_1: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B1") + pv_calculated_state_2: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B2") + pv_calculated_state_3: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B3") + pv_calculated_state_4: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B4") + pv_calculated_state_5: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B5") + pv_calculated_state_6: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B6") + pv_calculated_state_7: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B7") + pv_calculated_state_8: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B8") + pv_calculated_state_9: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B9") + pv_calculated_state_10: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BA") + pv_calculated_state_11: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BB") + pv_calculated_state_12: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BC") + pv_calculated_state_13: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BD") + pv_calculated_state_14: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BE") + pv_calculated_state_15: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BF") From 2b35cc9b9a9132cae87025c2a2df544eb326642c Mon Sep 17 00:00:00 2001 From: Oliver Silvester Date: Mon, 12 Jun 2023 13:21:38 +0100 Subject: [PATCH 03/12] Fix set_transmission --- src/dodal/devices/attenuator/attenuator.py | 122 ++++++++++----------- src/dodal/devices/attenuator/filter.py | 19 +--- 2 files changed, 58 insertions(+), 83 deletions(-) diff --git a/src/dodal/devices/attenuator/attenuator.py b/src/dodal/devices/attenuator/attenuator.py index 62297bccb1..b79a351e6d 100644 --- a/src/dodal/devices/attenuator/attenuator.py +++ b/src/dodal/devices/attenuator/attenuator.py @@ -3,6 +3,7 @@ from ophyd import Arming, Component, Device, EpicsSignal, Signal from ophyd.status import SubscriptionStatus +from dodal.devices.attenuator.filter import AtteunatorFilter from dodal.devices.detector import DetectorParams from dodal.devices.status import await_value from dodal.log import LOGGER @@ -15,41 +16,39 @@ def set(self, value, *, timeout=None, settle_time=None, **kwargs): do_set_transmission: TransmissionSignal = Component(TransmissionSignal) - # Could make a separate class for these, but that's potentially pointless as this is the only PV for it - calulated_filter_state_0: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B0") - calulated_filter_state_1: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B1") - calulated_filter_state_2: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B2") - calulated_filter_state_3: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B3") - calulated_filter_state_4: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B4") - calulated_filter_state_5: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B5") - calulated_filter_state_6: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B6") - calulated_filter_state_7: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B7") - calulated_filter_state_8: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B8") - calulated_filter_state_9: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B9") - calulated_filter_state_10: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BA") - calulated_filter_state_11: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BB") - calulated_filter_state_12: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BC") - calulated_filter_state_13: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BD") - calulated_filter_state_14: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BE") - calulated_filter_state_15: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BF") - - # Could also make another class for this - but again it's the only PV used - actual_filter_state_1: EpicsSignal = Component(EpicsSignal, ":FILTER1:INLIM") - actual_filter_state_2: EpicsSignal = Component(EpicsSignal, ":FILTER2:INLIM") - actual_filter_state_3: EpicsSignal = Component(EpicsSignal, ":FILTER3:INLIM") - actual_filter_state_4: EpicsSignal = Component(EpicsSignal, ":FILTER4:INLIM") - actual_filter_state_5: EpicsSignal = Component(EpicsSignal, ":FILTER5:INLIM") - actual_filter_state_6: EpicsSignal = Component(EpicsSignal, ":FILTER6:INLIM") - actual_filter_state_7: EpicsSignal = Component(EpicsSignal, ":FILTER7:INLIM") - actual_filter_state_8: EpicsSignal = Component(EpicsSignal, ":FILTER8:INLIM") - actual_filter_state_9: EpicsSignal = Component(EpicsSignal, ":FILTER9:INLIM") - actual_filter_state_10: EpicsSignal = Component(EpicsSignal, ":FILTER10:INLIM") - actual_filter_state_11: EpicsSignal = Component(EpicsSignal, ":FILTER11:INLIM") - actual_filter_state_12: EpicsSignal = Component(EpicsSignal, ":FILTER12:INLIM") - actual_filter_state_13: EpicsSignal = Component(EpicsSignal, ":FILTER13:INLIM") - actual_filter_state_14: EpicsSignal = Component(EpicsSignal, ":FILTER14:INLIM") - actual_filter_state_15: EpicsSignal = Component(EpicsSignal, ":FILTER15:INLIM") - actual_filter_state_16: EpicsSignal = Component(EpicsSignal, ":FILTER16:INLIM") + calulated_filter_state_1: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B0") + calulated_filter_state_2: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B1") + calulated_filter_state_3: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B2") + calulated_filter_state_4: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B3") + calulated_filter_state_5: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B4") + calulated_filter_state_6: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B5") + calulated_filter_state_7: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B6") + calulated_filter_state_8: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B7") + calulated_filter_state_9: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B8") + calulated_filter_state_10: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B9") + calulated_filter_state_11: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BA") + calulated_filter_state_12: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BB") + calulated_filter_state_13: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BC") + calulated_filter_state_14: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BD") + calulated_filter_state_15: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BE") + calulated_filter_state_16: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BF") + + filter_1: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER1") + filter_2: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER2") + filter_3: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER3") + filter_4: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER4") + filter_5: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER5") + filter_6: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER6") + filter_7: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER7") + filter_8: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER8") + filter_9: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER9") + filter_10: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER10") + filter_11: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER11") + filter_12: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER12") + filter_13: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER13") + filter_14: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER14") + filter_15: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER15") + filter_16: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER16") desired_transmission: EpicsSignal = Component(EpicsSignal, ":T2A:SETVAL1") use_current_energy: EpicsSignal = Component( @@ -62,7 +61,6 @@ def set(self, value, *, timeout=None, settle_time=None, **kwargs): def get_calculated_filter_state_list(self) -> list[EpicsSignal]: return [ - self.calulated_filter_state_0, self.calulated_filter_state_1, self.calulated_filter_state_2, self.calulated_filter_state_3, @@ -78,45 +76,41 @@ def get_calculated_filter_state_list(self) -> list[EpicsSignal]: self.calulated_filter_state_13, self.calulated_filter_state_14, self.calulated_filter_state_15, + self.calulated_filter_state_16, ] def get_actual_filter_state_list(self) -> list[EpicsSignal]: return [ - self.actual_filter_state_1, - self.actual_filter_state_2, - self.actual_filter_state_3, - self.actual_filter_state_4, - self.actual_filter_state_5, - self.actual_filter_state_6, - self.actual_filter_state_7, - self.actual_filter_state_8, - self.actual_filter_state_9, - self.actual_filter_state_10, - self.actual_filter_state_11, - self.actual_filter_state_12, - self.actual_filter_state_13, - self.actual_filter_state_14, - self.actual_filter_state_15, - self.actual_filter_state_16, + self.filter_1.actual_filter_state, + self.filter_2.actual_filter_state, + self.filter_3.actual_filter_state, + self.filter_4.actual_filter_state, + self.filter_5.actual_filter_state, + self.filter_6.actual_filter_state, + self.filter_7.actual_filter_state, + self.filter_8.actual_filter_state, + self.filter_9.actual_filter_state, + self.filter_10.actual_filter_state, + self.filter_11.actual_filter_state, + self.filter_12.actual_filter_state, + self.filter_13.actual_filter_state, + self.filter_14.actual_filter_state, + self.filter_15.actual_filter_state, + self.filter_16.actual_filter_state, ] def set_transmission(self, transmission) -> SubscriptionStatus: """Get desired states and calculated states, return a status which is complete once they are equal""" - # put this in try block? LOGGER.info("Using current energy") - self.pv_use_current_energy.put(1) + self.use_current_energy.put(1) LOGGER.info(f"Setting desired transmission to {transmission}") - self.pv_desired_transmission.put(transmission) + self.desired_transmission.put(transmission) LOGGER.info("Sending change filter command") - self.pv_change.put(1) + self.change.put(1) # Get desired filter positions (16phase) - desired_states = [] - - # Get the boolean desired state of each of the 16 filters - # TODO: put in a try catch block since the get might timeout for calculated_state in self.get_calculated_filter_state_list(): value = int(calculated_state.get(timeout=10)) desired_states.append(value == 1) @@ -126,11 +120,7 @@ def set_transmission(self, transmission) -> SubscriptionStatus: # Get the boolean actual state of each of the 16 filters for actual_state in self.get_actual_filter_state_list(): value = actual_state.get(timeout=10) - actual_states.append(value == "in") + actual_states.append(value == "In") + # Transmission is set when all actual and desired states are equal return await_value(actual_states, desired_states, timeout=30) - - # If all the actual states are equal to the desired state, we can move on - - # and check if atteunator is ready - # Get actual positions (get filter positions() from 16 phase) diff --git a/src/dodal/devices/attenuator/filter.py b/src/dodal/devices/attenuator/filter.py index 8002bdaaad..c4f2cd31be 100644 --- a/src/dodal/devices/attenuator/filter.py +++ b/src/dodal/devices/attenuator/filter.py @@ -1,22 +1,7 @@ -from ophyd import Component, Device, EpicsSignal, EpicsSignalRO +from ophyd import Component, Device, EpicsSignal from dodal.devices.attenuator.attenuator import Attenuator class AtteunatorFilter(Device): - pv_calculated_state_0: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B0") - pv_calculated_state_1: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B1") - pv_calculated_state_2: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B2") - pv_calculated_state_3: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B3") - pv_calculated_state_4: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B4") - pv_calculated_state_5: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B5") - pv_calculated_state_6: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B6") - pv_calculated_state_7: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B7") - pv_calculated_state_8: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B8") - pv_calculated_state_9: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B9") - pv_calculated_state_10: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BA") - pv_calculated_state_11: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BB") - pv_calculated_state_12: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BC") - pv_calculated_state_13: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BD") - pv_calculated_state_14: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BE") - pv_calculated_state_15: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BF") + actual_filter_state: EpicsSignal = Component(EpicsSignal, ":INLIM") From 6e9077b7166348930f6e898e8c690f4b623bb3f9 Mon Sep 17 00:00:00 2001 From: Oliver Silvester Date: Mon, 12 Jun 2023 16:34:38 +0100 Subject: [PATCH 04/12] Fixed transmission and added unit tests --- src/dodal/devices/attenuator/attenuator.py | 86 ++++++++++++--------- src/dodal/devices/attenuator/filter.py | 6 +- tests/devices/unit_tests/test_attenuator.py | 34 ++++++++ 3 files changed, 84 insertions(+), 42 deletions(-) create mode 100644 tests/devices/unit_tests/test_attenuator.py diff --git a/src/dodal/devices/attenuator/attenuator.py b/src/dodal/devices/attenuator/attenuator.py index b79a351e6d..e255bd1b07 100644 --- a/src/dodal/devices/attenuator/attenuator.py +++ b/src/dodal/devices/attenuator/attenuator.py @@ -1,7 +1,7 @@ from typing import Optional -from ophyd import Arming, Component, Device, EpicsSignal, Signal -from ophyd.status import SubscriptionStatus +from ophyd import Component, Device, EpicsSignal, EpicsSignalRO, Signal +from ophyd.status import Status, SubscriptionStatus from dodal.devices.attenuator.filter import AtteunatorFilter from dodal.devices.detector import DetectorParams @@ -16,22 +16,36 @@ def set(self, value, *, timeout=None, settle_time=None, **kwargs): do_set_transmission: TransmissionSignal = Component(TransmissionSignal) - calulated_filter_state_1: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B0") - calulated_filter_state_2: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B1") - calulated_filter_state_3: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B2") - calulated_filter_state_4: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B3") - calulated_filter_state_5: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B4") - calulated_filter_state_6: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B5") - calulated_filter_state_7: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B6") - calulated_filter_state_8: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B7") - calulated_filter_state_9: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B8") - calulated_filter_state_10: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.B9") - calulated_filter_state_11: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BA") - calulated_filter_state_12: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BB") - calulated_filter_state_13: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BC") - calulated_filter_state_14: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BD") - calulated_filter_state_15: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BE") - calulated_filter_state_16: EpicsSignal = Component(EpicsSignal, ":DEC_TO_BIN.BF") + calulated_filter_state_1: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B0") + calulated_filter_state_2: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B1") + calulated_filter_state_3: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B2") + calulated_filter_state_4: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B3") + calulated_filter_state_5: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B4") + calulated_filter_state_6: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B5") + calulated_filter_state_7: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B6") + calulated_filter_state_8: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B7") + calulated_filter_state_9: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B8") + calulated_filter_state_10: EpicsSignalRO = Component( + EpicsSignalRO, ":DEC_TO_BIN.B9" + ) + calulated_filter_state_11: EpicsSignalRO = Component( + EpicsSignalRO, ":DEC_TO_BIN.BA" + ) + calulated_filter_state_12: EpicsSignalRO = Component( + EpicsSignalRO, ":DEC_TO_BIN.BB" + ) + calulated_filter_state_13: EpicsSignalRO = Component( + EpicsSignalRO, ":DEC_TO_BIN.BC" + ) + calulated_filter_state_14: EpicsSignalRO = Component( + EpicsSignalRO, ":DEC_TO_BIN.BD" + ) + calulated_filter_state_15: EpicsSignalRO = Component( + EpicsSignalRO, ":DEC_TO_BIN.BE" + ) + calulated_filter_state_16: EpicsSignalRO = Component( + EpicsSignalRO, ":DEC_TO_BIN.BF" + ) filter_1: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER1") filter_2: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER2") @@ -52,7 +66,7 @@ def set(self, value, *, timeout=None, settle_time=None, **kwargs): desired_transmission: EpicsSignal = Component(EpicsSignal, ":T2A:SETVAL1") use_current_energy: EpicsSignal = Component( - EpicsSignal, ":E2WL:USECURRENTENERGY.PROC" + EpicsSignal, ":E2WL:USECURRENTENERY.PROC" ) change: EpicsSignal = Component(EpicsSignal, ":FANOUT") actual_transmission: EpicsSignal = Component(EpicsSignal, ":MATCH") @@ -103,24 +117,20 @@ def set_transmission(self, transmission) -> SubscriptionStatus: """Get desired states and calculated states, return a status which is complete once they are equal""" LOGGER.info("Using current energy") - self.use_current_energy.put(1) + self.use_current_energy.set(1).wait() LOGGER.info(f"Setting desired transmission to {transmission}") - self.desired_transmission.put(transmission) + self.desired_transmission.set(transmission).wait() LOGGER.info("Sending change filter command") - self.change.put(1) - - # Get desired filter positions (16phase) - desired_states = [] - for calculated_state in self.get_calculated_filter_state_list(): - value = int(calculated_state.get(timeout=10)) - desired_states.append(value == 1) - - actual_states = [] - - # Get the boolean actual state of each of the 16 filters - for actual_state in self.get_actual_filter_state_list(): - value = actual_state.get(timeout=10) - actual_states.append(value == "In") - - # Transmission is set when all actual and desired states are equal - return await_value(actual_states, desired_states, timeout=30) + self.change.set(1).wait() + + # At some point we need to check how and when the calculated states are set, since if this is ran beforehand + # ,the function won't work + status = Status(done=True, success=True) + actual_states = self.get_actual_filter_state_list() + calculated_states = self.get_calculated_filter_state_list() + for i in range(16): + status &= await_value( + actual_states[i], calculated_states[i].get(), timeout=10 + ) + + return status diff --git a/src/dodal/devices/attenuator/filter.py b/src/dodal/devices/attenuator/filter.py index c4f2cd31be..a794ccc715 100644 --- a/src/dodal/devices/attenuator/filter.py +++ b/src/dodal/devices/attenuator/filter.py @@ -1,7 +1,5 @@ -from ophyd import Component, Device, EpicsSignal - -from dodal.devices.attenuator.attenuator import Attenuator +from ophyd import Component, Device, EpicsSignalRO class AtteunatorFilter(Device): - actual_filter_state: EpicsSignal = Component(EpicsSignal, ":INLIM") + actual_filter_state: EpicsSignalRO = Component(EpicsSignalRO, ":INLIM") diff --git a/tests/devices/unit_tests/test_attenuator.py b/tests/devices/unit_tests/test_attenuator.py new file mode 100644 index 0000000000..7ccb758bfc --- /dev/null +++ b/tests/devices/unit_tests/test_attenuator.py @@ -0,0 +1,34 @@ +from unittest.mock import MagicMock + +import pytest +from ophyd.sim import make_fake_device +from ophyd.status import Status + +from dodal.devices.attenuator.attenuator import Attenuator + +CALCULATED_VALUE = 10 + + +@pytest.fixture +def fake_attenuator(): + FakeAttenuator: Attenuator = make_fake_device(Attenuator) + fake_attenuator: Attenuator = FakeAttenuator(name="aperture") + + def mock_apply_values(val: int): + actual_states = fake_attenuator.get_actual_filter_state_list() + calculated_states = fake_attenuator.get_calculated_filter_state_list() + for i in range(16): + calculated_states[i].sim_put( + CALCULATED_VALUE + ) # Ignore the actual calculation as this is EPICS layer + actual_states[i].sim_put(calculated_states[i].get()) + return Status(done=True, success=True) + + fake_attenuator.change.set = MagicMock(side_effect=mock_apply_values) + + return fake_attenuator + + +def test_set_transmission_success(fake_attenuator: Attenuator): + fake_attenuator.calulated_filter_state_10.sim_put(1) + fake_attenuator.set_transmission(1.0).wait(10) From 63481c7e9bbca003f6536ca780b8f5a0ada0be8a Mon Sep 17 00:00:00 2001 From: Oliver Silvester Date: Wed, 14 Jun 2023 14:24:35 +0100 Subject: [PATCH 05/12] fix naming --- tests/devices/unit_tests/test_attenuator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/devices/unit_tests/test_attenuator.py b/tests/devices/unit_tests/test_attenuator.py index 7ccb758bfc..abe1df9daa 100644 --- a/tests/devices/unit_tests/test_attenuator.py +++ b/tests/devices/unit_tests/test_attenuator.py @@ -12,7 +12,7 @@ @pytest.fixture def fake_attenuator(): FakeAttenuator: Attenuator = make_fake_device(Attenuator) - fake_attenuator: Attenuator = FakeAttenuator(name="aperture") + fake_attenuator: Attenuator = FakeAttenuator(name="attenuator") def mock_apply_values(val: int): actual_states = fake_attenuator.get_actual_filter_state_list() From d077d8b5fccc7af596f16f7c6a26478d7c960c4d Mon Sep 17 00:00:00 2001 From: Oliver Silvester Date: Fri, 16 Jun 2023 09:55:34 +0100 Subject: [PATCH 06/12] Move filter class into attenuator, minor test change --- src/dodal/devices/attenuator.py | 140 ++++++++++++++++++++ tests/devices/unit_tests/test_attenuator.py | 7 +- 2 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 src/dodal/devices/attenuator.py diff --git a/src/dodal/devices/attenuator.py b/src/dodal/devices/attenuator.py new file mode 100644 index 0000000000..54aeb846eb --- /dev/null +++ b/src/dodal/devices/attenuator.py @@ -0,0 +1,140 @@ +from typing import Optional + +from ophyd import Component, Device, EpicsSignal, EpicsSignalRO, Signal +from ophyd.status import Status, SubscriptionStatus + +from dodal.devices.detector import DetectorParams +from dodal.devices.status import await_value +from dodal.log import LOGGER + + +class AtteunatorFilter(Device): + actual_filter_state: EpicsSignalRO = Component(EpicsSignalRO, ":INLIM") + + +class Attenuator(Device): + class TransmissionSignal(Signal): + def set(self, value, *, timeout=None, settle_time=None, **kwargs): + return self.parent.set_transmission() + + # Set in the range 0-1 + transmission: TransmissionSignal = Component(TransmissionSignal) + + calulated_filter_state_1: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B0") + calulated_filter_state_2: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B1") + calulated_filter_state_3: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B2") + calulated_filter_state_4: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B3") + calulated_filter_state_5: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B4") + calulated_filter_state_6: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B5") + calulated_filter_state_7: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B6") + calulated_filter_state_8: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B7") + calulated_filter_state_9: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B8") + calulated_filter_state_10: EpicsSignalRO = Component( + EpicsSignalRO, ":DEC_TO_BIN.B9" + ) + calulated_filter_state_11: EpicsSignalRO = Component( + EpicsSignalRO, ":DEC_TO_BIN.BA" + ) + calulated_filter_state_12: EpicsSignalRO = Component( + EpicsSignalRO, ":DEC_TO_BIN.BB" + ) + calulated_filter_state_13: EpicsSignalRO = Component( + EpicsSignalRO, ":DEC_TO_BIN.BC" + ) + calulated_filter_state_14: EpicsSignalRO = Component( + EpicsSignalRO, ":DEC_TO_BIN.BD" + ) + calulated_filter_state_15: EpicsSignalRO = Component( + EpicsSignalRO, ":DEC_TO_BIN.BE" + ) + calulated_filter_state_16: EpicsSignalRO = Component( + EpicsSignalRO, ":DEC_TO_BIN.BF" + ) + + filter_1: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER1") + filter_2: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER2") + filter_3: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER3") + filter_4: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER4") + filter_5: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER5") + filter_6: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER6") + filter_7: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER7") + filter_8: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER8") + filter_9: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER9") + filter_10: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER10") + filter_11: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER11") + filter_12: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER12") + filter_13: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER13") + filter_14: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER14") + filter_15: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER15") + filter_16: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER16") + + desired_transmission: EpicsSignal = Component(EpicsSignal, ":T2A:SETVAL1") + use_current_energy: EpicsSignal = Component( + EpicsSignal, ":E2WL:USECURRENTENERY.PROC" + ) + change: EpicsSignal = Component(EpicsSignal, ":FANOUT") + actual_transmission: EpicsSignal = Component(EpicsSignal, ":MATCH") + + detector_params: Optional[DetectorParams] = None + + def get_calculated_filter_state_list(self) -> list[EpicsSignal]: + return [ + self.calulated_filter_state_1, + self.calulated_filter_state_2, + self.calulated_filter_state_3, + self.calulated_filter_state_4, + self.calulated_filter_state_5, + self.calulated_filter_state_6, + self.calulated_filter_state_7, + self.calulated_filter_state_8, + self.calulated_filter_state_9, + self.calulated_filter_state_10, + self.calulated_filter_state_11, + self.calulated_filter_state_12, + self.calulated_filter_state_13, + self.calulated_filter_state_14, + self.calulated_filter_state_15, + self.calulated_filter_state_16, + ] + + def get_actual_filter_state_list(self) -> list[EpicsSignal]: + return [ + self.filter_1.actual_filter_state, + self.filter_2.actual_filter_state, + self.filter_3.actual_filter_state, + self.filter_4.actual_filter_state, + self.filter_5.actual_filter_state, + self.filter_6.actual_filter_state, + self.filter_7.actual_filter_state, + self.filter_8.actual_filter_state, + self.filter_9.actual_filter_state, + self.filter_10.actual_filter_state, + self.filter_11.actual_filter_state, + self.filter_12.actual_filter_state, + self.filter_13.actual_filter_state, + self.filter_14.actual_filter_state, + self.filter_15.actual_filter_state, + self.filter_16.actual_filter_state, + ] + + def set_transmission(self, transmission) -> SubscriptionStatus: + """Get desired states and calculated states, return a status which is complete once they are equal""" + + LOGGER.info("Using current energy") + self.use_current_energy.set(1).wait() + LOGGER.info(f"Setting desired transmission to {transmission}") + self.desired_transmission.set(transmission).wait() + LOGGER.info("Sending change filter command") + self.change.set(1).wait() + + # At some point we need to check how and when the calculated states are set, since if this is ran beforehand + # ,the function won't work + status = Status(done=True, success=True) + actual_states = self.get_actual_filter_state_list() + calculated_states = self.get_calculated_filter_state_list() + for i in range(16): + status &= await_value( + actual_states[i], calculated_states[i].get(), timeout=10 + ) + + return status diff --git a/tests/devices/unit_tests/test_attenuator.py b/tests/devices/unit_tests/test_attenuator.py index abe1df9daa..b822fe935a 100644 --- a/tests/devices/unit_tests/test_attenuator.py +++ b/tests/devices/unit_tests/test_attenuator.py @@ -1,12 +1,13 @@ from unittest.mock import MagicMock +import numpy as np import pytest from ophyd.sim import make_fake_device from ophyd.status import Status -from dodal.devices.attenuator.attenuator import Attenuator +from dodal.devices.attenuator import Attenuator -CALCULATED_VALUE = 10 +CALCULATED_VALUE = np.random.randint(0, 10, size=16) @pytest.fixture @@ -19,7 +20,7 @@ def mock_apply_values(val: int): calculated_states = fake_attenuator.get_calculated_filter_state_list() for i in range(16): calculated_states[i].sim_put( - CALCULATED_VALUE + CALCULATED_VALUE[i] ) # Ignore the actual calculation as this is EPICS layer actual_states[i].sim_put(calculated_states[i].get()) return Status(done=True, success=True) From b6d95e8d0182c1976fe568f5e4b07a679f846b6e Mon Sep 17 00:00:00 2001 From: Oliver Silvester Date: Fri, 16 Jun 2023 09:56:33 +0100 Subject: [PATCH 07/12] remove old files --- src/dodal/devices/attenuator/attenuator.py | 136 --------------------- src/dodal/devices/attenuator/filter.py | 5 - 2 files changed, 141 deletions(-) delete mode 100644 src/dodal/devices/attenuator/attenuator.py delete mode 100644 src/dodal/devices/attenuator/filter.py diff --git a/src/dodal/devices/attenuator/attenuator.py b/src/dodal/devices/attenuator/attenuator.py deleted file mode 100644 index e255bd1b07..0000000000 --- a/src/dodal/devices/attenuator/attenuator.py +++ /dev/null @@ -1,136 +0,0 @@ -from typing import Optional - -from ophyd import Component, Device, EpicsSignal, EpicsSignalRO, Signal -from ophyd.status import Status, SubscriptionStatus - -from dodal.devices.attenuator.filter import AtteunatorFilter -from dodal.devices.detector import DetectorParams -from dodal.devices.status import await_value -from dodal.log import LOGGER - - -class Attenuator(Device): - class TransmissionSignal(Signal): - def set(self, value, *, timeout=None, settle_time=None, **kwargs): - return self.parent.set_transmission() - - do_set_transmission: TransmissionSignal = Component(TransmissionSignal) - - calulated_filter_state_1: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B0") - calulated_filter_state_2: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B1") - calulated_filter_state_3: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B2") - calulated_filter_state_4: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B3") - calulated_filter_state_5: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B4") - calulated_filter_state_6: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B5") - calulated_filter_state_7: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B6") - calulated_filter_state_8: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B7") - calulated_filter_state_9: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B8") - calulated_filter_state_10: EpicsSignalRO = Component( - EpicsSignalRO, ":DEC_TO_BIN.B9" - ) - calulated_filter_state_11: EpicsSignalRO = Component( - EpicsSignalRO, ":DEC_TO_BIN.BA" - ) - calulated_filter_state_12: EpicsSignalRO = Component( - EpicsSignalRO, ":DEC_TO_BIN.BB" - ) - calulated_filter_state_13: EpicsSignalRO = Component( - EpicsSignalRO, ":DEC_TO_BIN.BC" - ) - calulated_filter_state_14: EpicsSignalRO = Component( - EpicsSignalRO, ":DEC_TO_BIN.BD" - ) - calulated_filter_state_15: EpicsSignalRO = Component( - EpicsSignalRO, ":DEC_TO_BIN.BE" - ) - calulated_filter_state_16: EpicsSignalRO = Component( - EpicsSignalRO, ":DEC_TO_BIN.BF" - ) - - filter_1: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER1") - filter_2: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER2") - filter_3: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER3") - filter_4: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER4") - filter_5: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER5") - filter_6: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER6") - filter_7: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER7") - filter_8: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER8") - filter_9: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER9") - filter_10: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER10") - filter_11: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER11") - filter_12: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER12") - filter_13: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER13") - filter_14: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER14") - filter_15: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER15") - filter_16: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER16") - - desired_transmission: EpicsSignal = Component(EpicsSignal, ":T2A:SETVAL1") - use_current_energy: EpicsSignal = Component( - EpicsSignal, ":E2WL:USECURRENTENERY.PROC" - ) - change: EpicsSignal = Component(EpicsSignal, ":FANOUT") - actual_transmission: EpicsSignal = Component(EpicsSignal, ":MATCH") - - detector_params: Optional[DetectorParams] = None - - def get_calculated_filter_state_list(self) -> list[EpicsSignal]: - return [ - self.calulated_filter_state_1, - self.calulated_filter_state_2, - self.calulated_filter_state_3, - self.calulated_filter_state_4, - self.calulated_filter_state_5, - self.calulated_filter_state_6, - self.calulated_filter_state_7, - self.calulated_filter_state_8, - self.calulated_filter_state_9, - self.calulated_filter_state_10, - self.calulated_filter_state_11, - self.calulated_filter_state_12, - self.calulated_filter_state_13, - self.calulated_filter_state_14, - self.calulated_filter_state_15, - self.calulated_filter_state_16, - ] - - def get_actual_filter_state_list(self) -> list[EpicsSignal]: - return [ - self.filter_1.actual_filter_state, - self.filter_2.actual_filter_state, - self.filter_3.actual_filter_state, - self.filter_4.actual_filter_state, - self.filter_5.actual_filter_state, - self.filter_6.actual_filter_state, - self.filter_7.actual_filter_state, - self.filter_8.actual_filter_state, - self.filter_9.actual_filter_state, - self.filter_10.actual_filter_state, - self.filter_11.actual_filter_state, - self.filter_12.actual_filter_state, - self.filter_13.actual_filter_state, - self.filter_14.actual_filter_state, - self.filter_15.actual_filter_state, - self.filter_16.actual_filter_state, - ] - - def set_transmission(self, transmission) -> SubscriptionStatus: - """Get desired states and calculated states, return a status which is complete once they are equal""" - - LOGGER.info("Using current energy") - self.use_current_energy.set(1).wait() - LOGGER.info(f"Setting desired transmission to {transmission}") - self.desired_transmission.set(transmission).wait() - LOGGER.info("Sending change filter command") - self.change.set(1).wait() - - # At some point we need to check how and when the calculated states are set, since if this is ran beforehand - # ,the function won't work - status = Status(done=True, success=True) - actual_states = self.get_actual_filter_state_list() - calculated_states = self.get_calculated_filter_state_list() - for i in range(16): - status &= await_value( - actual_states[i], calculated_states[i].get(), timeout=10 - ) - - return status diff --git a/src/dodal/devices/attenuator/filter.py b/src/dodal/devices/attenuator/filter.py deleted file mode 100644 index a794ccc715..0000000000 --- a/src/dodal/devices/attenuator/filter.py +++ /dev/null @@ -1,5 +0,0 @@ -from ophyd import Component, Device, EpicsSignalRO - - -class AtteunatorFilter(Device): - actual_filter_state: EpicsSignalRO = Component(EpicsSignalRO, ":INLIM") From 7a1dca3d251d7ff28509a687f89c1f4f16215de2 Mon Sep 17 00:00:00 2001 From: Oliver Silvester Date: Fri, 16 Jun 2023 11:01:07 +0100 Subject: [PATCH 08/12] minor changes from review --- src/dodal/devices/attenuator.py | 2 -- tests/devices/unit_tests/test_attenuator.py | 12 ++++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/dodal/devices/attenuator.py b/src/dodal/devices/attenuator.py index 54aeb846eb..9620104532 100644 --- a/src/dodal/devices/attenuator.py +++ b/src/dodal/devices/attenuator.py @@ -127,8 +127,6 @@ def set_transmission(self, transmission) -> SubscriptionStatus: LOGGER.info("Sending change filter command") self.change.set(1).wait() - # At some point we need to check how and when the calculated states are set, since if this is ran beforehand - # ,the function won't work status = Status(done=True, success=True) actual_states = self.get_actual_filter_state_list() calculated_states = self.get_calculated_filter_state_list() diff --git a/tests/devices/unit_tests/test_attenuator.py b/tests/devices/unit_tests/test_attenuator.py index b822fe935a..5578a07102 100644 --- a/tests/devices/unit_tests/test_attenuator.py +++ b/tests/devices/unit_tests/test_attenuator.py @@ -1,13 +1,14 @@ from unittest.mock import MagicMock -import numpy as np import pytest +from bluesky import RunEngine +from bluesky import plan_stubs as bps from ophyd.sim import make_fake_device from ophyd.status import Status from dodal.devices.attenuator import Attenuator -CALCULATED_VALUE = np.random.randint(0, 10, size=16) +CALCULATED_VALUE = range(0, 17) @pytest.fixture @@ -31,5 +32,8 @@ def mock_apply_values(val: int): def test_set_transmission_success(fake_attenuator: Attenuator): - fake_attenuator.calulated_filter_state_10.sim_put(1) - fake_attenuator.set_transmission(1.0).wait(10) + fake_attenuator.set_transmission(1.0).wait(1) + + +def test_set_transmission_in_run_engine(fake_attenuator: Attenuator, RE: RunEngine): + yield from bps.abs_set(fake_attenuator.transmission, 1, wait=True) From 1655536e734282577836d87d57aaf35897a51710 Mon Sep 17 00:00:00 2001 From: Oliver Silvester Date: Fri, 16 Jun 2023 13:07:04 +0100 Subject: [PATCH 09/12] set_transmission is now just regular set override for attenuator --- src/dodal/devices/attenuator.py | 43 +++++++++------------ tests/devices/unit_tests/test_attenuator.py | 4 +- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/dodal/devices/attenuator.py b/src/dodal/devices/attenuator.py index 9620104532..c05cf0ea41 100644 --- a/src/dodal/devices/attenuator.py +++ b/src/dodal/devices/attenuator.py @@ -13,12 +13,25 @@ class AtteunatorFilter(Device): class Attenuator(Device): - class TransmissionSignal(Signal): - def set(self, value, *, timeout=None, settle_time=None, **kwargs): - return self.parent.set_transmission() + # Sets transmission - range 0-1 + def set(self, transmission) -> SubscriptionStatus: + """Get desired states and calculated states, return a status which is complete once they are equal""" - # Set in the range 0-1 - transmission: TransmissionSignal = Component(TransmissionSignal) + LOGGER.info("Using current energy") + self.use_current_energy.set(1).wait() + LOGGER.info(f"Setting desired transmission to {transmission}") + self.desired_transmission.set(transmission).wait() + LOGGER.info("Sending change filter command") + self.change.set(1).wait() + + status = Status(done=True, success=True) + actual_states = self.get_actual_filter_state_list() + calculated_states = self.get_calculated_filter_state_list() + for i in range(16): + status &= await_value( + actual_states[i], calculated_states[i].get(), timeout=10 + ) + return status calulated_filter_state_1: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B0") calulated_filter_state_2: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B1") @@ -116,23 +129,3 @@ def get_actual_filter_state_list(self) -> list[EpicsSignal]: self.filter_15.actual_filter_state, self.filter_16.actual_filter_state, ] - - def set_transmission(self, transmission) -> SubscriptionStatus: - """Get desired states and calculated states, return a status which is complete once they are equal""" - - LOGGER.info("Using current energy") - self.use_current_energy.set(1).wait() - LOGGER.info(f"Setting desired transmission to {transmission}") - self.desired_transmission.set(transmission).wait() - LOGGER.info("Sending change filter command") - self.change.set(1).wait() - - status = Status(done=True, success=True) - actual_states = self.get_actual_filter_state_list() - calculated_states = self.get_calculated_filter_state_list() - for i in range(16): - status &= await_value( - actual_states[i], calculated_states[i].get(), timeout=10 - ) - - return status diff --git a/tests/devices/unit_tests/test_attenuator.py b/tests/devices/unit_tests/test_attenuator.py index 5578a07102..8b91568d74 100644 --- a/tests/devices/unit_tests/test_attenuator.py +++ b/tests/devices/unit_tests/test_attenuator.py @@ -32,8 +32,8 @@ def mock_apply_values(val: int): def test_set_transmission_success(fake_attenuator: Attenuator): - fake_attenuator.set_transmission(1.0).wait(1) + fake_attenuator.set(1.0).wait(1) def test_set_transmission_in_run_engine(fake_attenuator: Attenuator, RE: RunEngine): - yield from bps.abs_set(fake_attenuator.transmission, 1, wait=True) + yield from bps.abs_set(fake_attenuator, 1, wait=True) From afb20c677086d7a8202e31760e13502188231e4d Mon Sep 17 00:00:00 2001 From: Oliver Silvester Date: Fri, 16 Jun 2023 13:12:21 +0100 Subject: [PATCH 10/12] linting --- src/dodal/devices/attenuator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dodal/devices/attenuator.py b/src/dodal/devices/attenuator.py index c05cf0ea41..a8b6c79c1c 100644 --- a/src/dodal/devices/attenuator.py +++ b/src/dodal/devices/attenuator.py @@ -1,6 +1,6 @@ from typing import Optional -from ophyd import Component, Device, EpicsSignal, EpicsSignalRO, Signal +from ophyd import Component, Device, EpicsSignal, EpicsSignalRO from ophyd.status import Status, SubscriptionStatus from dodal.devices.detector import DetectorParams From a0498c288de026babcfabba64aa6c4fd0f61774b Mon Sep 17 00:00:00 2001 From: Oliver Silvester Date: Wed, 21 Jun 2023 16:59:49 +0100 Subject: [PATCH 11/12] extra test runs in RunEngine --- tests/devices/unit_tests/test_attenuator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/devices/unit_tests/test_attenuator.py b/tests/devices/unit_tests/test_attenuator.py index 8b91568d74..35f966c066 100644 --- a/tests/devices/unit_tests/test_attenuator.py +++ b/tests/devices/unit_tests/test_attenuator.py @@ -35,5 +35,6 @@ def test_set_transmission_success(fake_attenuator: Attenuator): fake_attenuator.set(1.0).wait(1) -def test_set_transmission_in_run_engine(fake_attenuator: Attenuator, RE: RunEngine): - yield from bps.abs_set(fake_attenuator, 1, wait=True) +def test_set_transmission_in_run_engine(fake_attenuator: Attenuator): + RE = RunEngine() + RE(bps.abs_set(fake_attenuator, 1, wait=True)) From 270662f8e458aee301384c5647b0ac92b2af2e36 Mon Sep 17 00:00:00 2001 From: Oliver Silvester Date: Thu, 22 Jun 2023 16:15:33 +0100 Subject: [PATCH 12/12] fix attenuator PVs --- src/dodal/devices/attenuator.py | 86 ++++++++++++++------------------- 1 file changed, 36 insertions(+), 50 deletions(-) diff --git a/src/dodal/devices/attenuator.py b/src/dodal/devices/attenuator.py index a8b6c79c1c..75a6b32154 100644 --- a/src/dodal/devices/attenuator.py +++ b/src/dodal/devices/attenuator.py @@ -33,60 +33,46 @@ def set(self, transmission) -> SubscriptionStatus: ) return status - calulated_filter_state_1: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B0") - calulated_filter_state_2: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B1") - calulated_filter_state_3: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B2") - calulated_filter_state_4: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B3") - calulated_filter_state_5: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B4") - calulated_filter_state_6: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B5") - calulated_filter_state_7: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B6") - calulated_filter_state_8: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B7") - calulated_filter_state_9: EpicsSignalRO = Component(EpicsSignalRO, ":DEC_TO_BIN.B8") - calulated_filter_state_10: EpicsSignalRO = Component( - EpicsSignalRO, ":DEC_TO_BIN.B9" - ) - calulated_filter_state_11: EpicsSignalRO = Component( - EpicsSignalRO, ":DEC_TO_BIN.BA" - ) - calulated_filter_state_12: EpicsSignalRO = Component( - EpicsSignalRO, ":DEC_TO_BIN.BB" - ) - calulated_filter_state_13: EpicsSignalRO = Component( - EpicsSignalRO, ":DEC_TO_BIN.BC" - ) - calulated_filter_state_14: EpicsSignalRO = Component( - EpicsSignalRO, ":DEC_TO_BIN.BD" - ) - calulated_filter_state_15: EpicsSignalRO = Component( - EpicsSignalRO, ":DEC_TO_BIN.BE" - ) - calulated_filter_state_16: EpicsSignalRO = Component( - EpicsSignalRO, ":DEC_TO_BIN.BF" - ) + calulated_filter_state_1: EpicsSignalRO = Component(EpicsSignalRO, "DEC_TO_BIN.B0") + calulated_filter_state_2: EpicsSignalRO = Component(EpicsSignalRO, "DEC_TO_BIN.B1") + calulated_filter_state_3: EpicsSignalRO = Component(EpicsSignalRO, "DEC_TO_BIN.B2") + calulated_filter_state_4: EpicsSignalRO = Component(EpicsSignalRO, "DEC_TO_BIN.B3") + calulated_filter_state_5: EpicsSignalRO = Component(EpicsSignalRO, "DEC_TO_BIN.B4") + calulated_filter_state_6: EpicsSignalRO = Component(EpicsSignalRO, "DEC_TO_BIN.B5") + calulated_filter_state_7: EpicsSignalRO = Component(EpicsSignalRO, "DEC_TO_BIN.B6") + calulated_filter_state_8: EpicsSignalRO = Component(EpicsSignalRO, "DEC_TO_BIN.B7") + calulated_filter_state_9: EpicsSignalRO = Component(EpicsSignalRO, "DEC_TO_BIN.B8") + calulated_filter_state_10: EpicsSignalRO = Component(EpicsSignalRO, "DEC_TO_BIN.B9") + calulated_filter_state_11: EpicsSignalRO = Component(EpicsSignalRO, "DEC_TO_BIN.BA") + calulated_filter_state_12: EpicsSignalRO = Component(EpicsSignalRO, "DEC_TO_BIN.BB") + calulated_filter_state_13: EpicsSignalRO = Component(EpicsSignalRO, "DEC_TO_BIN.BC") + calulated_filter_state_14: EpicsSignalRO = Component(EpicsSignalRO, "DEC_TO_BIN.BD") + calulated_filter_state_15: EpicsSignalRO = Component(EpicsSignalRO, "DEC_TO_BIN.BE") + calulated_filter_state_16: EpicsSignalRO = Component(EpicsSignalRO, "DEC_TO_BIN.BF") - filter_1: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER1") - filter_2: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER2") - filter_3: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER3") - filter_4: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER4") - filter_5: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER5") - filter_6: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER6") - filter_7: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER7") - filter_8: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER8") - filter_9: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER9") - filter_10: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER10") - filter_11: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER11") - filter_12: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER12") - filter_13: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER13") - filter_14: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER14") - filter_15: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER15") - filter_16: AtteunatorFilter = Component(AtteunatorFilter, ":FILTER16") + filter_1: AtteunatorFilter = Component(AtteunatorFilter, "FILTER1") + filter_2: AtteunatorFilter = Component(AtteunatorFilter, "FILTER2") + filter_3: AtteunatorFilter = Component(AtteunatorFilter, "FILTER3") + filter_4: AtteunatorFilter = Component(AtteunatorFilter, "FILTER4") + filter_5: AtteunatorFilter = Component(AtteunatorFilter, "FILTER5") + filter_6: AtteunatorFilter = Component(AtteunatorFilter, "FILTER6") + filter_7: AtteunatorFilter = Component(AtteunatorFilter, "FILTER7") + filter_8: AtteunatorFilter = Component(AtteunatorFilter, "FILTER8") + filter_9: AtteunatorFilter = Component(AtteunatorFilter, "FILTER9") + filter_10: AtteunatorFilter = Component(AtteunatorFilter, "FILTER10") + filter_11: AtteunatorFilter = Component(AtteunatorFilter, "FILTER11") + filter_12: AtteunatorFilter = Component(AtteunatorFilter, "FILTER12") + filter_13: AtteunatorFilter = Component(AtteunatorFilter, "FILTER13") + filter_14: AtteunatorFilter = Component(AtteunatorFilter, "FILTER14") + filter_15: AtteunatorFilter = Component(AtteunatorFilter, "FILTER15") + filter_16: AtteunatorFilter = Component(AtteunatorFilter, "FILTER16") - desired_transmission: EpicsSignal = Component(EpicsSignal, ":T2A:SETVAL1") + desired_transmission: EpicsSignal = Component(EpicsSignal, "T2A:SETVAL1") use_current_energy: EpicsSignal = Component( - EpicsSignal, ":E2WL:USECURRENTENERY.PROC" + EpicsSignal, "E2WL:USECURRENTENERY.PROC" ) - change: EpicsSignal = Component(EpicsSignal, ":FANOUT") - actual_transmission: EpicsSignal = Component(EpicsSignal, ":MATCH") + change: EpicsSignal = Component(EpicsSignal, "FANOUT") + actual_transmission: EpicsSignal = Component(EpicsSignal, "MATCH") detector_params: Optional[DetectorParams] = None