Skip to content
75 changes: 64 additions & 11 deletions src/dodal/devices/synchrotron.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ class Prefix(str, Enum):
class Suffix(str, Enum):
SIGNAL = "SIGNAL"
MODE = "MODE"
USER_COUNTDOWN = "USERCOUNTDN"
BEAM_ENERGY = "BEAMENERGY"
USERCOUNTDOWN = "USERCOUNTDN"
BEAMENERGY = "BEAMENERGY"
COUNTDOWN = "COUNTDOWN"
END_COUNTDOWN = "ENDCOUNTDN"
STACOUNTDN = "STACOUNTDN"
ENDCOUNTDOWN = "ENDCOUNTDN"
TOPUPSTATE = "TOPUPSTATE"
FILLPERIOD = "FILLPERIOD"


class SynchrotronMode(StrictEnum):
Expand All @@ -35,18 +38,66 @@ class SynchrotronMode(StrictEnum):
UNKNOWN = "Unknown"


class TopUpState(StrictEnum):
OFF = "Off"
RUNNING = "Running"
STOPPING = "Stopping"
FAILED = "Failed"
NO_PROCESS = "No process"


class Synchrotron(StandardReadable):
"""A StandardReadable device that represents a synchrotron facility, providing
access to various synchrotron parameters and operational status.

Args:
signal_prefix (str, optional): Beamline part of PV. Defaults to Prefix.SIGNAL.
status_prefix (str, optional): Status part of PV. Defaults to Prefix.STATUS.
topup_prefix (str, optional): Top-up part of PV. Defaults to Prefix.TOP_UP.
Comment on lines +54 to +56
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I always thought having these as arguments was kind of silly. When would we ever change them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably will never change them, but if someone wants to reuse our code in a different synchrotron they would need it. Not that it is difficult to add though... That's the only reasoning I see behind it. But I get a feeling that we don't have such purpose of having this repo reused. Or do we?

name (str, optional): Name of the device. Defaults to an empty string.

Attributes:
current (SignalR[float]): Read-only signal for the synchrotron ring current.
Standard optional field in NXsource component of nexus file.
energy (SignalR[float]): Read-only signal for the synchrotron ring energy.
Standard optional field in NXsource component of nexus file.
probe (SignalR[str]): Read-only signal for the probe type. Defaults to
``"x-ray"``. Standard optional field in NXsource component of nexus file.
type (SignalR[str]): Read-only signal for the synchrotron type. Defaults to
``"Synchrotron X-ray Source"``. Standard optional field in NXsource
component of nexus file.
synchrotron_mode (SignalR[SynchrotronMode]): current state of the synchrotron
ring. Standard optional field ``"mode"`` in NXsource component of nexus file.
machine_user_countdown (SignalR[float]): If current mode in user, counts down
the number of seconds until the end of the current period.
top_up_start_countdown (SignalR[float]): counter that runs to zero at the start
of TopUp and remains there until the fill is complete when it resets to time
before next TopUp fill. Used in data acquisition to avoid beam intensity
change during topup.
top_up_countdown (SignalR[float]): counter that runs to zero at the start
of TopUp fill and is reset immediately to the time to next TopUp fill once
TopUp starts.
top_up_end_countdown (SignalR[float]): counter that runs to zero at the end of
TopUp fill and resets immediately to an estimate of the time before the end
of the next TopUp fill. Used in data acquisition to avoid beam intensity
change during topup.
fill_period (signalR[float]): time between TopUp (in minutes).Used in data
acquisition to monitor synchrotron state.
top_up_state (signalR[TopUpState]): tracks whether TopUp is currently running or
not. If it is anything other than Running or Stopping TopUp isn't active so
will not disturb the beam. Used in data acquisition to confirm TopUp state.
"""

def __init__(
self,
signal_prefix: str = Prefix.SIGNAL,
status_prefix: str = Prefix.STATUS,
topup_prefix: str = Prefix.TOP_UP,
name: str = "",
*,
signal_prefix=Prefix.SIGNAL,
status_prefix=Prefix.STATUS,
topup_prefix=Prefix.TOP_UP,
):
with self.add_children_as_readables():
self.current = epics_signal_r(float, signal_prefix + Suffix.SIGNAL)
self.energy = epics_signal_r(float, status_prefix + Suffix.BEAM_ENERGY)
self.energy = epics_signal_r(float, status_prefix + Suffix.BEAMENERGY)

with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
self.probe, _ = soft_signal_r_and_setter(str, initial_value="x-ray")
Expand All @@ -57,13 +108,15 @@ def __init__(
SynchrotronMode, status_prefix + Suffix.MODE
)
self.machine_user_countdown = epics_signal_r(
float, status_prefix + Suffix.USER_COUNTDOWN
float, status_prefix + Suffix.USERCOUNTDOWN
)
self.top_up_start_countdown = epics_signal_r(
float, topup_prefix + Suffix.COUNTDOWN
)
self.top_up_countdown = epics_signal_r(float, topup_prefix + Suffix.STACOUNTDN)
self.top_up_end_countdown = epics_signal_r(
float, topup_prefix + Suffix.END_COUNTDOWN
float, topup_prefix + Suffix.ENDCOUNTDOWN
)

self.fill_period = epics_signal_r(float, topup_prefix + Suffix.FILLPERIOD)
self.top_up_state = epics_signal_r(TopUpState, topup_prefix + Suffix.TOPUPSTATE)
super().__init__(name=name)