Skip to content

Commit

Permalink
Merge pull request #988 from spc-group/undulator
Browse files Browse the repository at this point in the history
Wrote new support for the planar undulator.
  • Loading branch information
prjemian authored Jul 1, 2024
2 parents a9b07a1 + d5833dd commit 791afbc
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 56 deletions.
3 changes: 1 addition & 2 deletions apstools/devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@

from .aps_machine import ApsMachineParametersDevice

from .aps_undulator import ApsUndulator
from .aps_undulator import ApsUndulatorDual
from .aps_undulator import PlanarUndulator

from .area_detector_support import AD_EpicsFileNameMixin
from .area_detector_support import AD_FrameType_schemes
Expand Down
120 changes: 66 additions & 54 deletions apstools/devices/aps_undulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,81 +8,93 @@
~ApsUndulatorDual
"""

import logging
from enum import IntEnum

from ophyd import Component
from ophyd import DerivedSignal
from ophyd import Device
from ophyd import EpicsSignal
from ophyd import EpicsSignalRO
from ophyd import PVPositioner
from ophyd import Signal

from .tracking_signal import TrackingSignal

logger = logging.getLogger(__name__)

class ApsUndulator(Device):
"""
APS Undulator

.. index:: Ophyd Device; ApsUndulator
class DoneStatus(IntEnum):
MOVING = 0
DONE = 1

EXAMPLE::

undulator = ApsUndulator("ID09ds:", name="undulator")
"""
class BusyStatus(IntEnum):
DONE = 0
BUSY = 1


class MotorDriveStatus(IntEnum):
NOT_READY = 0
READY_TO_MOVE = 1


class UndulatorPositioner(PVPositioner):
"""A positioner for any of the gap control parameters.
Communicates with the parent (presumably the undulator device) to
start and stop the device.
energy = Component(
EpicsSignal,
"Energy",
write_pv="EnergySet",
put_complete=True,
kind="hinted",
)
energy_taper = Component(
EpicsSignal,
"TaperEnergy",
write_pv="TaperEnergySet",
kind="config",
)
gap = Component(EpicsSignal, "Gap", write_pv="GapSet")
gap_taper = Component(EpicsSignal, "TaperGap", write_pv="TaperGapSet", kind="config")
start_button = Component(EpicsSignal, "Start", put_complete=True, kind="omitted")
stop_button = Component(EpicsSignal, "Stop", kind="omitted")
harmonic_value = Component(EpicsSignal, "HarmonicValue", kind="config")
gap_deadband = Component(EpicsSignal, "DeadbandGap", kind="config")
device_limit = Component(EpicsSignal, "DeviceLimit", kind="config")

access_mode = Component(EpicsSignalRO, "AccessSecurity", kind="omitted")
device_status = Component(EpicsSignalRO, "Busy", kind="omitted")
total_power = Component(EpicsSignalRO, "TotalPower", kind="config")
message1 = Component(EpicsSignalRO, "Message1", kind="omitted")
message2 = Component(EpicsSignalRO, "Message2", kind="omitted")
message3 = Component(EpicsSignalRO, "Message3", kind="omitted")
time_left = Component(EpicsSignalRO, "ShClosedTime", kind="omitted")

device = Component(EpicsSignalRO, "Device", kind="config")
location = Component(EpicsSignalRO, "Location", kind="config")
version = Component(EpicsSignalRO, "Version", kind="config")

# Useful undulator parameters that are not EPICS PVs.
energy_deadband = Component(Signal, value=0.0, kind="config")
energy_backlash = Component(Signal, value=0.0, kind="config")
energy_offset = Component(Signal, value=0, kind="config")
tracking = Component(TrackingSignal, value=False, kind="config")


class ApsUndulatorDual(Device):
"""
APS Undulator with upstream *and* downstream controls

.. index:: Ophyd Device; ApsUndulatorDual
setpoint = Component(EpicsSignal, "SetC.VAL")
readback = Component(EpicsSignalRO, "M.VAL")

actuate = Component(DerivedSignal, derived_from="parent.start_button", kind="omitted")
stop_signal = Component(DerivedSignal, derived_from="parent.stop_button", kind="omitted")
done = Component(DerivedSignal, derived_from="parent.done", kind="omitted")
done_value = DoneStatus.DONE


class PlanarUndulator(Device):
"""APS Planar Undulator.
.. index:: Ophyd Device; PlanarUndulator
The signals *busy* and *done* convey complementary
information. *busy* comes from the IOC, while *done* comes
directly from the controller.
EXAMPLE::
undulator = ApsUndulatorDual("ID09", name="undulator")
undulator = PlanarUndulator("S25ID:USID:", name="undulator")
note:: the trailing ``:`` in the PV prefix should be omitted
"""

upstream = Component(ApsUndulator, "us:")
downstream = Component(ApsUndulator, "ds:")
# X-ray spectrum parameters
energy = Component(UndulatorPositioner, "Energy")
energy_taper = Component(UndulatorPositioner, "TaperEnergy")
gap = Component(UndulatorPositioner, "Gap")
gap_taper = Component(UndulatorPositioner, "TaperGap")
harmonic_value = Component(EpicsSignal, "HarmonicValueC", kind="config")
total_power = Component(EpicsSignalRO, "TotalPowerM.VAL", kind="config")
# Signals for moving the undulator
start_button = Component(EpicsSignal, "StartC.VAL", put_complete=True, kind="omitted")
stop_button = Component(EpicsSignal, "StopC.VAL", kind="omitted")
busy = Component(EpicsSignalRO, "BusyM.VAL", kind="omitted")
done = Component(EpicsSignalRO, "BusyDeviceM.VAL", kind="omitted")
motor_drive_status = Component(EpicsSignalRO, "MotorDriveStatusM.VAL", kind="omitted")
# Miscellaneous control signals
gap_deadband = Component(EpicsSignal, "DeadbandGapC", kind="config")
device_limit = Component(EpicsSignal, "DeviceLimitM.VAL", kind="config")
access_mode = Component(EpicsSignalRO, "AccessSecurityC", kind="omitted")
message1 = Component(EpicsSignalRO, "Message1M.VAL", kind="omitted")
message2 = Component(EpicsSignalRO, "Message2M.VAL", kind="omitted")
device = Component(EpicsSignalRO, "DeviceM", kind="config")
magnet = Component(EpicsSignalRO, "DeviceMagnetM", kind="config")
location = Component(EpicsSignalRO, "LocationM", kind="config")
version_plc = Component(EpicsSignalRO, "PLCVersionM.VAL", kind="config")
version_hpmu = Component(EpicsSignalRO, "HPMUVersionM.VAL", kind="config")


# -----------------------------------------------------------------------------
Expand Down
23 changes: 23 additions & 0 deletions apstools/devices/tests/test_aps_undulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import pytest
from ophyd.sim import instantiate_fake_device

from ..aps_undulator import PlanarUndulator


@pytest.fixture()
def undulator():
undulator = instantiate_fake_device(PlanarUndulator, prefix="PSS:255ID:", name="undulator")
return undulator


def test_set_energy(undulator):
assert undulator.start_button.get() == 0
undulator.energy.set(5)
assert undulator.energy.setpoint.get() == 5
assert undulator.start_button.get() == 1


def test_stop_energy(undulator):
assert undulator.stop_button.get() == 0
undulator.stop()
assert undulator.stop_button.get() == 1

0 comments on commit 791afbc

Please sign in to comment.