diff --git a/src/firefly/controller.py b/src/firefly/controller.py index dcde7359..4bc6f427 100644 --- a/src/firefly/controller.py +++ b/src/firefly/controller.py @@ -7,12 +7,13 @@ import pydm import pyqtgraph as pg import qtawesome as qta +from ophyd_async.core import NotConnected from ophydregistry import Registry 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,14 @@ 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() 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..3bc6252c 100644 --- a/src/haven/positioner.py +++ b/src/haven/positioner.py @@ -49,10 +49,19 @@ 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() + 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 async def set(self, value: float, timeout: CalculatableTimeout = CALCULATE_TIMEOUT): @@ -71,6 +80,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 +97,12 @@ 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)) + 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: # Monitor based on readback position diff --git a/src/haven/tests/test_energy_positioner.py b/src/haven/tests/test_energy_positioner.py index fc3d4c43..e44727f2 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..7601e83f 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 BusyStatus, PlanarUndulator @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 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 -