From ae96f5b34d662425d4373bb598d388b6c1e908b3 Mon Sep 17 00:00:00 2001 From: Mark Wolfman Date: Sat, 7 Dec 2024 08:40:54 -0600 Subject: [PATCH 1/4] Added back the shutter selector to the voltmeters display. --- src/firefly/tests/test_voltmeters.py | 50 +++++++++++++++++++++++++--- src/firefly/voltmeters.py | 7 +++- src/firefly/voltmeters.ui | 28 ++++++++++++++-- 3 files changed, 78 insertions(+), 7 deletions(-) diff --git a/src/firefly/tests/test_voltmeters.py b/src/firefly/tests/test_voltmeters.py index a1b2fc57..1ff41a07 100644 --- a/src/firefly/tests/test_voltmeters.py +++ b/src/firefly/tests/test_voltmeters.py @@ -1,5 +1,8 @@ +import asyncio + import pytest from bluesky_queueserver_api import BPlan +from ophyd_async.core import Device from pydm import widgets as PyDMWidgets from pydm.widgets.analog_indicator import PyDMAnalogIndicator from qtpy import QtWidgets @@ -29,7 +32,20 @@ async def ion_chambers(sim_registry): @pytest.fixture() -async def voltmeters_display(qtbot, ion_chambers, sim_registry): +async def shutters(sim_registry): + shutters = [ + Device(name="front_end_shutter"), + Device(name="enstation_shutter"), + ] + await asyncio.gather(*[d.connect(mock=True) for d in shutters]) + for shutter in shutters: + shutter._ophyd_labels_ = {"shutters"} + sim_registry.register(shutter) + return shutters + + +@pytest.fixture() +async def voltmeters_display(qtbot, ion_chambers, shutters, sim_registry): vms_display = VoltmetersDisplay() qtbot.addWidget(vms_display) await vms_display.update_devices(sim_registry) @@ -153,12 +169,38 @@ def check_item(item): @pytest.mark.asyncio async def test_read_dark_current_plan(voltmeters_display, qtbot): + display = voltmeters_display + display.ui.shutter_checkbox.setChecked(False) + # Check that the correct plan was sent + expected_item = BPlan("record_dark_current", ["I0", "It"], shutters=[]) + + def check_item(item): + return item.to_dict() == expected_item.to_dict() + + # Click the run button and see if the plan is queued + with qtbot.waitSignal( + display.queue_item_submitted, timeout=1000, check_params_cb=check_item + ): + # Simulate clicking on the dark_current button + # display.ui.dark_current_button.click() + display.ui.record_dark_current() + + +def test_shutters_checkbox(voltmeters_display): + display = voltmeters_display + combobox = display.ui.shutter_combobox + combobox_items = [combobox.itemText(idx) for idx in range(combobox.count())] + assert "front_end_shutter" in combobox_items + + +@pytest.mark.asyncio +async def test_read_dark_current_plan_with_shutters(voltmeters_display, qtbot): display = voltmeters_display display.ui.shutter_checkbox.setChecked(True) + display.ui.shutter_combobox.setCurrentIndex(1) # Check that the correct plan was sent - expected_item = BPlan( - "record_dark_current", ["I0", "It"], shutters=["experiment_shutter"] - ) + shutter_name = display.ui.shutter_combobox.itemText(1) + expected_item = BPlan("record_dark_current", ["I0", "It"], shutters=[shutter_name]) def check_item(item): return item.to_dict() == expected_item.to_dict() diff --git a/src/firefly/voltmeters.py b/src/firefly/voltmeters.py index 15772899..ce10e7e0 100644 --- a/src/firefly/voltmeters.py +++ b/src/firefly/voltmeters.py @@ -71,6 +71,10 @@ async def update_devices(self, registry): # Connect the details button signal details_slot = partial(self.details_window_requested.emit, ic.name) row.details_button.clicked.connect(details_slot) + # Add the shutters to the shutter combobox + shutters = registry.findall("shutters") + for shutter in shutters: + self.ui.shutter_combobox.addItem(shutter.name) def update_queue_status(self, status): super().update_queue_status(status) @@ -106,7 +110,8 @@ def record_dark_current(self): # Determine which shutters to close shutters = [] if self.ui.shutter_checkbox.isChecked(): - shutters.append("experiment_shutter") + shutter_name = self.ui.shutter_combobox.currentText() + shutters.append(shutter_name) # Construct the plan ic_names = [ic.name for ic in self.ion_chambers] item = BPlan("record_dark_current", ic_names, shutters=shutters) diff --git a/src/firefly/voltmeters.ui b/src/firefly/voltmeters.ui index c391a414..89b786a4 100644 --- a/src/firefly/voltmeters.ui +++ b/src/firefly/voltmeters.ui @@ -87,7 +87,7 @@ - false + true <html><head/><body><p>Close the experimental experimental hutch shutter prior to recording dark current. </p><p><br/></p><p><span style=" font-weight:600;">This feature is disabled pending availability of shutter controls.</span></p></body></html> @@ -103,6 +103,13 @@ + + + + false + + + @@ -224,5 +231,22 @@ - + + + shutter_checkbox + toggled(bool) + shutter_combobox + setEnabled(bool) + + + 157 + 53 + + + 249 + 53 + + + + From 18a004cd9d2425c097e6a248e68ccfe6a01c8bd7 Mon Sep 17 00:00:00 2001 From: Mark Wolfman Date: Sun, 8 Dec 2024 15:32:00 -0600 Subject: [PATCH 2/4] Voltmeters window now handles the case where no shutters are found in the registry. --- src/firefly/tests/test_voltmeters.py | 43 ++++++++++++++++++++++------ src/firefly/voltmeters.py | 22 ++++++++++---- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/firefly/tests/test_voltmeters.py b/src/firefly/tests/test_voltmeters.py index 1ff41a07..1579eb5e 100644 --- a/src/firefly/tests/test_voltmeters.py +++ b/src/firefly/tests/test_voltmeters.py @@ -45,7 +45,7 @@ async def shutters(sim_registry): @pytest.fixture() -async def voltmeters_display(qtbot, ion_chambers, shutters, sim_registry): +async def voltmeters_display(qtbot, ion_chambers, sim_registry): vms_display = VoltmetersDisplay() qtbot.addWidget(vms_display) await vms_display.update_devices(sim_registry) @@ -167,12 +167,44 @@ def check_item(item): display.ui.auto_gain_button.click() +async def test_shutters_checkbox_no_shutters(voltmeters_display, sim_registry): + display = voltmeters_display + combobox = display.ui.shutter_combobox + checkbox = display.ui.shutter_checkbox + checkbox.setChecked(True) + # Update the state of the UI with no shutters + await display.update_devices(sim_registry) + # Ensure checkbox has been disabled + assert not checkbox.isEnabled() + assert not checkbox.checkState() + assert not display.ui.shutter_checkbox.setChecked(True) + combobox_items = [combobox.itemText(idx) for idx in range(combobox.count())] + assert len(combobox_items) == 0 + + +async def test_shutters_checkbox_with_shutters( + voltmeters_display, + sim_registry, + shutters, +): + display = voltmeters_display + checkbox = display.ui.shutter_checkbox + combobox = display.ui.shutter_combobox + # Update the state of the UI with no shutters + await display.update_devices(sim_registry) + # Ensure checkbox has been enabled + assert checkbox.isEnabled() + # Check that shutters were added to the combobox + combobox_items = [combobox.itemText(idx) for idx in range(combobox.count())] + assert "front_end_shutter" in combobox_items + + @pytest.mark.asyncio async def test_read_dark_current_plan(voltmeters_display, qtbot): display = voltmeters_display display.ui.shutter_checkbox.setChecked(False) # Check that the correct plan was sent - expected_item = BPlan("record_dark_current", ["I0", "It"], shutters=[]) + expected_item = BPlan("record_dark_current", ["I0", "It"]) def check_item(item): return item.to_dict() == expected_item.to_dict() @@ -186,13 +218,6 @@ def check_item(item): display.ui.record_dark_current() -def test_shutters_checkbox(voltmeters_display): - display = voltmeters_display - combobox = display.ui.shutter_combobox - combobox_items = [combobox.itemText(idx) for idx in range(combobox.count())] - assert "front_end_shutter" in combobox_items - - @pytest.mark.asyncio async def test_read_dark_current_plan_with_shutters(voltmeters_display, qtbot): display = voltmeters_display diff --git a/src/firefly/voltmeters.py b/src/firefly/voltmeters.py index ce10e7e0..f43f42d3 100644 --- a/src/firefly/voltmeters.py +++ b/src/firefly/voltmeters.py @@ -71,10 +71,20 @@ async def update_devices(self, registry): # Connect the details button signal details_slot = partial(self.details_window_requested.emit, ic.name) row.details_button.clicked.connect(details_slot) + # Remove old shutters from the combobox + for idx in range(self.ui.shutter_combobox.count()): + self.ui.shutter_combobox.removeItem(idx) # Add the shutters to the shutter combobox - shutters = registry.findall("shutters") - for shutter in shutters: - self.ui.shutter_combobox.addItem(shutter.name) + shutters = registry.findall("shutters", allow_none=True) + has_shutters = bool(len(shutters)) + if has_shutters: + self.ui.shutter_checkbox.setEnabled(True) + for shutter in shutters: + self.ui.shutter_combobox.addItem(shutter.name) + else: + log.warning("No shutters found, disabling checkbox.") + self.ui.shutter_checkbox.setEnabled(False) + self.ui.shutter_checkbox.setCheckState(False) def update_queue_status(self, status): super().update_queue_status(status) @@ -108,13 +118,13 @@ def record_dark_current(self): """ # Determine which shutters to close - shutters = [] + kwargs = {} if self.ui.shutter_checkbox.isChecked(): shutter_name = self.ui.shutter_combobox.currentText() - shutters.append(shutter_name) + kwargs["shutters"] = [shutter_name] # Construct the plan ic_names = [ic.name for ic in self.ion_chambers] - item = BPlan("record_dark_current", ic_names, shutters=shutters) + item = BPlan("record_dark_current", ic_names, **kwargs) # Send it to the queue server self.queue_item_submitted.emit(item) From 4d522138d2fd750c759998d24e203791f2e076bb Mon Sep 17 00:00:00 2001 From: Mark Wolfman Date: Thu, 19 Dec 2024 12:14:52 -0600 Subject: [PATCH 3/4] Voltmeters display only shows shutters that can be both opened and closed. --- src/firefly/tests/test_voltmeters.py | 11 ++++++----- src/firefly/voltmeters.py | 10 +++++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/firefly/tests/test_voltmeters.py b/src/firefly/tests/test_voltmeters.py index 1579eb5e..afd68b30 100644 --- a/src/firefly/tests/test_voltmeters.py +++ b/src/firefly/tests/test_voltmeters.py @@ -33,10 +33,10 @@ async def ion_chambers(sim_registry): @pytest.fixture() async def shutters(sim_registry): - shutters = [ - Device(name="front_end_shutter"), - Device(name="enstation_shutter"), - ] + front_end_shutter = Device(name="front_end_shutter") + front_end_shutter.allow_close = False + endstation_shutter = Device(name="endstation_shutter") + shutters = [front_end_shutter, endstation_shutter] await asyncio.gather(*[d.connect(mock=True) for d in shutters]) for shutter in shutters: shutter._ophyd_labels_ = {"shutters"} @@ -196,7 +196,8 @@ async def test_shutters_checkbox_with_shutters( assert checkbox.isEnabled() # Check that shutters were added to the combobox combobox_items = [combobox.itemText(idx) for idx in range(combobox.count())] - assert "front_end_shutter" in combobox_items + assert "front_end_shutter" not in combobox_items + assert "endstation_shutter" in combobox_items @pytest.mark.asyncio diff --git a/src/firefly/voltmeters.py b/src/firefly/voltmeters.py index f43f42d3..e0e56fba 100644 --- a/src/firefly/voltmeters.py +++ b/src/firefly/voltmeters.py @@ -74,9 +74,17 @@ async def update_devices(self, registry): # Remove old shutters from the combobox for idx in range(self.ui.shutter_combobox.count()): self.ui.shutter_combobox.removeItem(idx) - # Add the shutters to the shutter combobox + + # Decide which shutters we should show (only those that can be opened/closed) + def is_controllable(shtr): + can_open = getattr(shtr, "allow_open", True) + can_close = getattr(shtr, "allow_close", True) + return can_open and can_close + shutters = registry.findall("shutters", allow_none=True) + shutters = [shtr for shtr in shutters if is_controllable(shtr)] has_shutters = bool(len(shutters)) + # Add the shutters to the shutter combobox if has_shutters: self.ui.shutter_checkbox.setEnabled(True) for shutter in shutters: From 15d1e89d96b89bc6220c222a792ba393d3dc1b5d Mon Sep 17 00:00:00 2001 From: Mark Wolfman Date: Thu, 19 Dec 2024 12:29:04 -0600 Subject: [PATCH 4/4] Pinned apsbss version to <2 until we can get Haven updated to match breaking changes. --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 325f1dc2..ab545c1d 100644 --- a/environment.yml +++ b/environment.yml @@ -41,7 +41,7 @@ dependencies: - aioca - aiokafka - asynctest - - apsbss + - apsbss < 2.0 - time-machine - rich - autoapi