From 7adb17deee270066b4ca7bf65cf5655a1b03a506 Mon Sep 17 00:00:00 2001 From: yannachen Date: Wed, 16 Oct 2024 10:03:11 -0500 Subject: [PATCH 1/5] Fixed the done tracking for the energy positioner. --- src/haven/devices/energy_positioner.py | 2 +- src/haven/devices/xray_source.py | 4 ++-- src/haven/ipython_startup.ipy | 1 + src/haven/positioner.py | 13 +++++++++---- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/haven/devices/energy_positioner.py b/src/haven/devices/energy_positioner.py index 27d6f85b..2ea02e0e 100644 --- a/src/haven/devices/energy_positioner.py +++ b/src/haven/devices/energy_positioner.py @@ -89,7 +89,7 @@ def __init__( float, derived_from={"velocity": self.monochromator.energy.velocity} ) - super().__init__(name=name) + super().__init__(name=name, put_complete=True) async def set_energy(self, value, mono: Signal, undulator: Signal): ev_per_kev = 1000 diff --git a/src/haven/devices/xray_source.py b/src/haven/devices/xray_source.py index 502173f4..ebc1e7f6 100644 --- a/src/haven/devices/xray_source.py +++ b/src/haven/devices/xray_source.py @@ -32,7 +32,7 @@ class MotorDriveStatus(IntEnum): class UndulatorPositioner(Positioner): - done_value: int = DoneStatus.DONE + done_value: int = BusyStatus.DONE def __init__( self, @@ -106,7 +106,7 @@ def __init__(self, prefix: str, name: str = ""): f"{prefix}Energy", actuate_signal=self.start_button, stop_signal=self.stop_button, - done_signal=self.done, + done_signal=self.busy, ) self.energy_taper = UndulatorPositioner( f"{prefix}TaperEnergy", diff --git a/src/haven/ipython_startup.ipy b/src/haven/ipython_startup.ipy index b26e5a94..7b0beff6 100644 --- a/src/haven/ipython_startup.ipy +++ b/src/haven/ipython_startup.ipy @@ -62,6 +62,7 @@ print(f"Connected to {num_devices} devices in {time.monotonic() - t0:.2f} second # Save references to some commonly used things in the global namespace registry = haven.beamline.registry ion_chambers = registry.findall("ion_chambers", allow_none=True) +energy = registry['energy'] # Print helpful information to the console custom_theme = Theme( diff --git a/src/haven/positioner.py b/src/haven/positioner.py index 66f95109..56bbd800 100644 --- a/src/haven/positioner.py +++ b/src/haven/positioner.py @@ -49,10 +49,14 @@ def set_name(self, name: str): # Readback should be named the same as its parent in read() self.readback.set_name(name) - def watch_done(self, value, event): + def watch_done(self, value, done_event: asyncio.Event, started_event: asyncio.Event): """Update the event when the done value is actually done.""" - if value == self.done_value: - event.set() + if value != self.done_value: + # The movement has started + started_event.set() + elif started_event.is_set(): + # Move has finished + done_event.set() @WatchableAsyncStatus.wrap async def set(self, value: float, timeout: CalculatableTimeout = CALCULATE_TIMEOUT): @@ -71,6 +75,7 @@ async def set(self, value: float, timeout: CalculatableTimeout = CALCULATE_TIMEO # error if not done in time reached_setpoint = asyncio.Event() done_event = asyncio.Event() + started_event = asyncio.Event() # Start the move if hasattr(self, "actuate"): # Set the setpoint, then click "go" @@ -87,7 +92,7 @@ async def set(self, value: float, timeout: CalculatableTimeout = CALCULATE_TIMEO done_status = set_status elif hasattr(self, "done"): # Monitor the `done` signal - self.done.subscribe_value(partial(self.watch_done, event=done_event)) + self.done.subscribe_value(partial(self.watch_done, done_event=done_event, started_event=started_event)) done_status = AsyncStatus(asyncio.wait_for(done_event.wait(), timeout)) else: # Monitor based on readback position From a33d3d7aab639e621cdcaf4f3bbf76126d3fb7dc Mon Sep 17 00:00:00 2001 From: Mark Wolfman Date: Wed, 16 Oct 2024 10:10:37 -0500 Subject: [PATCH 2/5] Updated systemd units to have two dependency trees (kafka vs queueserver). --- src/queueserver/systemd_units/mongo_consumer.service | 7 +++---- src/queueserver/systemd_units/queueserver.service | 3 --- src/queueserver/systemd_units/redis.service | 1 - 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/queueserver/systemd_units/mongo_consumer.service b/src/queueserver/systemd_units/mongo_consumer.service index f537c163..911f632e 100644 --- a/src/queueserver/systemd_units/mongo_consumer.service +++ b/src/queueserver/systemd_units/mongo_consumer.service @@ -1,12 +1,11 @@ [Unit] Description=consumer for saving bluesky documents to database via kafka After=syslog.target network.target -Wants=queueserver.service -After=queueserver.service +Wants=kafka.service +After=kafka.service [Service] -Environment="BLUESKY_DIR=%h/bluesky" -ExecStart=/bin/bash -l -c 'micromamba activate haven && env python %h/src/queueserver/mongo_consumer.py' +ExecStart=/bin/bash -l -c 'mamba activate haven && env python %h/src/queueserver/mongo_consumer.py' # ExecStopPost=/APSshare/epics/base-7.0.7/bin/rhel8-x86_64/caput 100id:bluesky:mongo_consumer_state 1 # ExecStartPost=/APSshare/epics/base-7.0.7/bin/rhel8-x86_64/caput 100id:bluesky:mongo_consumer_state 2 diff --git a/src/queueserver/systemd_units/queueserver.service b/src/queueserver/systemd_units/queueserver.service index cf23a108..41caeb6b 100644 --- a/src/queueserver/systemd_units/queueserver.service +++ b/src/queueserver/systemd_units/queueserver.service @@ -1,12 +1,9 @@ [Unit] Description=Bluesky Queue Server (25-ID-C) -Wants=kafka.service -After=kafka.service Requires=redis.service After=redis.service [Service] -Environment="BLUESKY_DIR=%h/bluesky" ExecStart=/bin/bash -l -c 'micromamba activate haven && %h/src/queueserver/queueserver.sh' # ExecStopPost=/APSshare/epics/base-7.0.7/bin/rhel8-x86_64/caput 100id:bluesky:queueserver_state 1 # ExecStartPost=/APSshare/epics/base-7.0.7/bin/rhel8-x86_64/caput 100id:bluesky:queueserver_state 2 diff --git a/src/queueserver/systemd_units/redis.service b/src/queueserver/systemd_units/redis.service index 828c6749..7a1b9175 100644 --- a/src/queueserver/systemd_units/redis.service +++ b/src/queueserver/systemd_units/redis.service @@ -13,4 +13,3 @@ RuntimeDirectoryMode=0755 [Install] WantedBy=multi-user.target - From 3fb384e7cc445c4524d22d70241864988b19e3d5 Mon Sep 17 00:00:00 2001 From: yannachen Date: Wed, 16 Oct 2024 10:23:52 -0500 Subject: [PATCH 3/5] Added handling of non-connected devices during Firefly startup. --- src/firefly/controller.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/firefly/controller.py b/src/firefly/controller.py index dcde7359..4d49c4d0 100644 --- a/src/firefly/controller.py +++ b/src/firefly/controller.py @@ -8,11 +8,12 @@ import pyqtgraph as pg import qtawesome as qta from ophydregistry import Registry +from ophyd_async.core import NotConnected from qasync import asyncSlot from qtpy import QtCore, QtWidgets from qtpy.QtCore import Signal from qtpy.QtGui import QIcon, QKeySequence -from qtpy.QtWidgets import QAction +from qtpy.QtWidgets import QAction, QErrorMessage from haven import beamline, load_config from haven.device import titelize @@ -71,6 +72,8 @@ def __init__(self, parent=None, display="status", use_main_window=False): self.windows = OrderedDict() self.queue_re_state_changed.connect(self.enable_queue_controls) self.registry = beamline.registry + # An error message dialog for later use + self.error_message = QErrorMessage() def _setup_window_action( self, action_name: str, text: str, slot: QtCore.Slot, shortcut=None, icon=None @@ -103,7 +106,12 @@ async def setup_instrument(self, load_instrument=True): """ if load_instrument: - await beamline.load() + try: + await beamline.load() + except NotConnected as exc: + log.exception(exc) + msg = "One or more devices failed to load. See console logs for details." + self.error_message.showMessage(msg) self.registry_changed.emit(beamline.registry) # Make actions for launching other windows self.setup_window_actions() From b52b528c62613d547b380be1066208e3f89aa2d2 Mon Sep 17 00:00:00 2001 From: Mark Wolfman Date: Wed, 16 Oct 2024 11:08:09 -0500 Subject: [PATCH 4/5] Fixed tests. --- src/haven/positioner.py | 4 ++++ src/haven/tests/test_energy_positioner.py | 13 +++++++++---- src/haven/tests/test_positioner.py | 4 ++++ src/haven/tests/test_preprocessors.py | 2 +- src/haven/tests/test_xray_source.py | 8 ++++++-- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/haven/positioner.py b/src/haven/positioner.py index 56bbd800..7112a747 100644 --- a/src/haven/positioner.py +++ b/src/haven/positioner.py @@ -51,11 +51,14 @@ def set_name(self, name: str): def watch_done(self, value, done_event: asyncio.Event, started_event: asyncio.Event): """Update the event when the done value is actually done.""" + print(f"Received new done value: {value}.") if value != self.done_value: # The movement has started + print("Setting started_event") started_event.set() elif started_event.is_set(): # Move has finished + print("Setting done_event") done_event.set() @WatchableAsyncStatus.wrap @@ -92,6 +95,7 @@ async def set(self, value: float, timeout: CalculatableTimeout = CALCULATE_TIMEO done_status = set_status elif hasattr(self, "done"): # Monitor the `done` signal + print(f"Monitoring progress via ``done`` signal: {self.done.name}.") self.done.subscribe_value(partial(self.watch_done, done_event=done_event, started_event=started_event)) done_status = AsyncStatus(asyncio.wait_for(done_event.wait(), timeout)) else: diff --git a/src/haven/tests/test_energy_positioner.py b/src/haven/tests/test_energy_positioner.py index fc3d4c43..ce0a06f7 100644 --- a/src/haven/tests/test_energy_positioner.py +++ b/src/haven/tests/test_energy_positioner.py @@ -1,7 +1,10 @@ +import asyncio + import pytest from ophyd_async.core import set_mock_value from haven.devices.energy_positioner import EnergyPositioner +from haven.devices.xray_source import BusyStatus @pytest.fixture() @@ -17,15 +20,17 @@ async def positioner(): async def test_set_energy(positioner): # Set up dependent values - await positioner.monochromator.id_offset.set(150) + set_mock_value(positioner.monochromator.id_offset, 150) # Change the energy status = positioner.set(10000, timeout=3) - # Trick the positioner into being done - set_mock_value(positioner.undulator.energy.done, 1) + # Trick the Undulator into being done + set_mock_value(positioner.undulator.energy.done, BusyStatus.BUSY) + await asyncio.sleep(0.01) # Let the event loop run + set_mock_value(positioner.undulator.energy.done, BusyStatus.DONE) await status # Check that all the sub-components were set properly assert await positioner.monochromator.energy.user_setpoint.get_value() == 10000 - assert positioner.undulator.energy.get().setpoint == 10.150 + assert await positioner.undulator.energy.setpoint.get_value() == 10.150 async def test_real_to_pseudo_positioner(positioner): diff --git a/src/haven/tests/test_positioner.py b/src/haven/tests/test_positioner.py index d5bfa688..ebf06f0d 100644 --- a/src/haven/tests/test_positioner.py +++ b/src/haven/tests/test_positioner.py @@ -1,3 +1,5 @@ +import asyncio + import pytest from ophyd_async.core import set_mock_value from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw, epics_signal_x @@ -35,6 +37,8 @@ def test_has_signals(positioner): async def test_set_with_done_actuate(positioner): status = positioner.set(5.3) + set_mock_value(positioner.done, 0) + await asyncio.sleep(0.01) # Let event loop run set_mock_value(positioner.done, 1) await status diff --git a/src/haven/tests/test_preprocessors.py b/src/haven/tests/test_preprocessors.py index 6ad8ec6c..6ade7be4 100644 --- a/src/haven/tests/test_preprocessors.py +++ b/src/haven/tests/test_preprocessors.py @@ -169,7 +169,7 @@ def test_metadata(sim_registry, aps, monkeypatch): "EPICS_HOST_ARCH": "PDP11", "beamline_id": "SPC Beamline (sector unknown)", "facility_id": "advanced_photon_source", - "xray_source": "undulator: ID255ds:", + "xray_source": "undulator", "epics_libca": "/dev/null", "EPICS_CA_MAX_ARRAY_BYTES": "16", "scan_id": 1, diff --git a/src/haven/tests/test_xray_source.py b/src/haven/tests/test_xray_source.py index 2565fab1..40cfe24f 100644 --- a/src/haven/tests/test_xray_source.py +++ b/src/haven/tests/test_xray_source.py @@ -1,7 +1,9 @@ +import asyncio + import pytest from ophyd_async.core import get_mock_put, set_mock_value -from haven.devices.xray_source import PlanarUndulator +from haven.devices.xray_source import PlanarUndulator, BusyStatus @pytest.fixture() @@ -15,7 +17,9 @@ async def test_set_energy(undulator): # Set the energy status = undulator.energy.set(5) # Fake the done PV getting updated - set_mock_value(undulator.energy.done, 1) + set_mock_value(undulator.energy.done, BusyStatus.BUSY) + await asyncio.sleep(0.01) # Let the event loop run + set_mock_value(undulator.energy.done, BusyStatus.DONE) # Check that the signals got set properly await status assert await undulator.energy.setpoint.get_value() == 5 From ac527ae1a01a8e918ec0075943722a41fa1e6d06 Mon Sep 17 00:00:00 2001 From: Mark Wolfman Date: Wed, 16 Oct 2024 11:08:44 -0500 Subject: [PATCH 5/5] Black, isort, and flake8. --- src/firefly/controller.py | 6 ++++-- src/haven/positioner.py | 10 ++++++++-- src/haven/tests/test_energy_positioner.py | 2 +- src/haven/tests/test_xray_source.py | 2 +- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/firefly/controller.py b/src/firefly/controller.py index 4d49c4d0..4bc6f427 100644 --- a/src/firefly/controller.py +++ b/src/firefly/controller.py @@ -7,8 +7,8 @@ import pydm import pyqtgraph as pg import qtawesome as qta -from ophydregistry import Registry from ophyd_async.core import NotConnected +from ophydregistry import Registry from qasync import asyncSlot from qtpy import QtCore, QtWidgets from qtpy.QtCore import Signal @@ -110,7 +110,9 @@ async def setup_instrument(self, load_instrument=True): await beamline.load() except NotConnected as exc: log.exception(exc) - msg = "One or more devices failed to load. See console logs for details." + msg = ( + "One or more devices failed to load. See console logs for details." + ) self.error_message.showMessage(msg) self.registry_changed.emit(beamline.registry) # Make actions for launching other windows diff --git a/src/haven/positioner.py b/src/haven/positioner.py index 7112a747..3bc6252c 100644 --- a/src/haven/positioner.py +++ b/src/haven/positioner.py @@ -49,7 +49,9 @@ def set_name(self, name: str): # Readback should be named the same as its parent in read() self.readback.set_name(name) - def watch_done(self, value, done_event: asyncio.Event, started_event: asyncio.Event): + def watch_done( + self, value, done_event: asyncio.Event, started_event: asyncio.Event + ): """Update the event when the done value is actually done.""" print(f"Received new done value: {value}.") if value != self.done_value: @@ -96,7 +98,11 @@ async def set(self, value: float, timeout: CalculatableTimeout = CALCULATE_TIMEO elif hasattr(self, "done"): # Monitor the `done` signal print(f"Monitoring progress via ``done`` signal: {self.done.name}.") - self.done.subscribe_value(partial(self.watch_done, done_event=done_event, started_event=started_event)) + self.done.subscribe_value( + partial( + self.watch_done, done_event=done_event, started_event=started_event + ) + ) done_status = AsyncStatus(asyncio.wait_for(done_event.wait(), timeout)) else: # Monitor based on readback position diff --git a/src/haven/tests/test_energy_positioner.py b/src/haven/tests/test_energy_positioner.py index ce0a06f7..e44727f2 100644 --- a/src/haven/tests/test_energy_positioner.py +++ b/src/haven/tests/test_energy_positioner.py @@ -26,7 +26,7 @@ async def test_set_energy(positioner): # Trick the Undulator into being done set_mock_value(positioner.undulator.energy.done, BusyStatus.BUSY) await asyncio.sleep(0.01) # Let the event loop run - set_mock_value(positioner.undulator.energy.done, BusyStatus.DONE) + set_mock_value(positioner.undulator.energy.done, BusyStatus.DONE) await status # Check that all the sub-components were set properly assert await positioner.monochromator.energy.user_setpoint.get_value() == 10000 diff --git a/src/haven/tests/test_xray_source.py b/src/haven/tests/test_xray_source.py index 40cfe24f..7601e83f 100644 --- a/src/haven/tests/test_xray_source.py +++ b/src/haven/tests/test_xray_source.py @@ -3,7 +3,7 @@ import pytest from ophyd_async.core import get_mock_put, set_mock_value -from haven.devices.xray_source import PlanarUndulator, BusyStatus +from haven.devices.xray_source import BusyStatus, PlanarUndulator @pytest.fixture()