Skip to content

Commit

Permalink
Merge pull request #273 from spc-group/async-energy
Browse files Browse the repository at this point in the history
- Improved reliability of the energy positioner
- Error handling when loading devices in Firefly
  • Loading branch information
canismarko authored Oct 16, 2024
2 parents 2f144e0 + ac527ae commit d421d9d
Show file tree
Hide file tree
Showing 12 changed files with 58 additions and 24 deletions.
14 changes: 12 additions & 2 deletions src/firefly/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion src/haven/devices/energy_positioner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/haven/devices/xray_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class MotorDriveStatus(IntEnum):


class UndulatorPositioner(Positioner):
done_value: int = DoneStatus.DONE
done_value: int = BusyStatus.DONE

def __init__(
self,
Expand Down Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/haven/ipython_startup.ipy
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
23 changes: 19 additions & 4 deletions src/haven/positioner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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"
Expand All @@ -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
Expand Down
13 changes: 9 additions & 4 deletions src/haven/tests/test_energy_positioner.py
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -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):
Expand Down
4 changes: 4 additions & 0 deletions src/haven/tests/test_positioner.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/haven/tests/test_preprocessors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 6 additions & 2 deletions src/haven/tests/test_xray_source.py
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -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
Expand Down
7 changes: 3 additions & 4 deletions src/queueserver/systemd_units/mongo_consumer.service
Original file line number Diff line number Diff line change
@@ -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

Expand Down
3 changes: 0 additions & 3 deletions src/queueserver/systemd_units/queueserver.service
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 0 additions & 1 deletion src/queueserver/systemd_units/redis.service
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,3 @@ RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target

0 comments on commit d421d9d

Please sign in to comment.