Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
90d5e19
Update ID gap motor to use StandardMovable interface
oliwenmandiamond Aug 5, 2026
7d496f1
Update UndulatorGap to move the setpoint only
oliwenmandiamond Aug 5, 2026
fa06102
Improve async methods
oliwenmandiamond Aug 5, 2026
e1ac9b6
Fix tests
oliwenmandiamond Aug 5, 2026
292c3d1
Add doc strings
oliwenmandiamond Aug 5, 2026
0400e9e
Tidy up
oliwenmandiamond Aug 5, 2026
7f4626c
Break down into smaller files so easier to manage
oliwenmandiamond Aug 5, 2026
3f343c7
Update docs
oliwenmandiamond Aug 5, 2026
e6acf68
Fix remaining tests
oliwenmandiamond Aug 5, 2026
e25c765
Correct imports
oliwenmandiamond Aug 5, 2026
3857ab1
Fix dodal connect
oliwenmandiamond Aug 5, 2026
09f4fa5
Fix test
oliwenmandiamond Aug 5, 2026
727dd8d
Add stop unstoppable motor test
oliwenmandiamond Aug 6, 2026
145b592
Merge branch 'main' into update_id_logic_to_use_standard_movable
oliwenmandiamond Aug 6, 2026
01e3211
make id gap a motor
Relm-Arrowny Aug 6, 2026
6cf47eb
remote motor
Relm-Arrowny Aug 7, 2026
3163c4f
correct gate message
Relm-Arrowny Aug 7, 2026
58f9452
Correct typo in comment regarding phase and gap
Relm-Arrowny Aug 11, 2026
fc03a73
Merge branch 'main' into update_id_logic_to_use_standard_movable
oliwenmandiamond Aug 13, 2026
0eac5fd
Merge branch 'main' into update_id_logic_to_use_standard_movable
oliwenmandiamond Aug 13, 2026
ae4c233
Merge branch 'update_id_logic_to_use_standard_movable' of ssh://githu…
oliwenmandiamond Aug 14, 2026
20383ba
Create UndulatorAccessControl Device (#2174)
oliwenmandiamond Aug 14, 2026
bf870b8
Update set_move to use SignalW
oliwenmandiamond Aug 14, 2026
ae01228
Remove outdated comment
oliwenmandiamond Aug 14, 2026
5fdda83
Revert "Create UndulatorAccessControl Device (#2174)"
oliwenmandiamond Aug 17, 2026
8a9361d
Merge branch 'update_id_logic_to_use_standard_movable' into id_gap_motor
oliwenmandiamond Aug 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/dodal/devices/beamlines/i10/i10_apple2.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ async def _set_linear_arbitrary_angle(self, pol_angle: float) -> None:
f"jaw_phase position for angle ({pol_angle}) is outside permitted range"
f" [-{self.jaw_phase_limit}, {self.jaw_phase_limit}]"
)
await self.apple2().jaw_phase().set(jaw_phase)
await self.apple2_ref().jaw_phase().set(jaw_phase)
await self._linear_arbitrary_angle.set(pol_angle)

def _get_apple2_value(self, gap: float, phase: float, pol: Pol) -> Apple2Val:
Expand All @@ -144,8 +144,8 @@ async def _set_motors_from_energy_and_polarisation(
) -> None:
await super()._set_motors_from_energy_and_polarisation(energy, pol)
if pol != Pol.LA:
await self.apple2().jaw_phase().set(0)
await self.apple2().jaw_phase().set_move.set(1)
await self.apple2_ref().jaw_phase().set(0)
await self.apple2_ref().jaw_phase().set_move.set(1)

def _raise_if_not_la(self, pol: Pol) -> None:
if pol != Pol.LA:
Expand Down
23 changes: 7 additions & 16 deletions src/dodal/devices/insertion_device/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,18 @@
Apple2Type,
EnergyMotorConvertor,
)
from .apple2_undulator import (
DEFAULT_MOTOR_MIN_TIMEOUT,
Apple2,
from .apple2_undulator import Apple2, Apple2Val
from .apple2_undulator_base import SafeUndulatorMoverBase
from .apple2_undulator_gap import UndulatorGap
from .apple2_undulator_phase_axes import (
Apple2LockedPhasesVal,
Apple2PhasesVal,
Apple2Val,
EnabledDisabledUpper,
UndulatorGap,
UndulatorJawPhase,
UndulatorLockedPhaseAxes,
UndulatorPhaseAxes,
UnstoppableMotor,
)
from .apple_knot_controller import (
AppleKnotController,
AppleKnotPathFinder,
)
from .energy import BeamEnergy, InsertionDeviceEnergy, InsertionDeviceEnergyBase
from .apple_knot_controller import AppleKnotController, AppleKnotPathFinder
from .energy import BeamEnergy, InsertionDeviceEnergy
from .energy_motor_lookup import (
ConfigServerEnergyMotorLookup,
EnergyMotorLookup,
Expand All @@ -50,6 +44,7 @@
"AppleKnotPathFinder",
"UndulatorGap",
"UndulatorPhaseAxes",
"SafeUndulatorMoverBase",
"UndulatorJawPhase",
"Apple2Val",
"Apple2PhasesVal",
Expand All @@ -63,16 +58,12 @@
"UndulatorLockedPhaseAxes",
"EnergyCoverage",
"Pol",
"DEFAULT_MOTOR_MIN_TIMEOUT",
"EnabledDisabledUpper",
"UndulatorGateStatus",
"Apple2LockedPhasesVal",
"EnergyMotorLookup",
"ConfigServerEnergyMotorLookup",
"EpicsPolynomialEnergyMotorLookup",
"EnergyMotorConvertor",
"UnstoppableMotor",
"InsertionDeviceEnergyBase",
"EnergyCoverageEntry",
"MAXIMUM_ROW_PHASE_MOTOR_POSITION",
"MAXIMUM_GAP_MOTOR_POSITION",
Expand Down
17 changes: 8 additions & 9 deletions src/dodal/devices/insertion_device/apple2_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@
soft_signal_rw,
)

from dodal.devices.insertion_device.apple2_undulator import (
Apple2,
from dodal.devices.insertion_device.apple2_undulator import Apple2, Apple2Val
from dodal.devices.insertion_device.apple2_undulator_phase_axes import (
Apple2PhasesVal,
Apple2Val,
PhaseAxesType,
UndulatorPhaseAxes,
)
Expand Down Expand Up @@ -59,7 +58,7 @@ class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
motor positions via a user-supplied conversion callable.

Attributes:
apple2 (Reference[Apple2Type]): Reference to the Apple2 device containing gap
apple2_ref (Reference[Apple2Type]): Reference to the Apple2 device containing gap
and phase motors.
energy (derived_signal_rw): Derived signal for moving and reading back energy.
polarisation_setpoint (SignalR): Soft signal for the polarisation setpoint.
Expand Down Expand Up @@ -110,7 +109,7 @@ def __init__(
units: str = "eV",
name: str = "",
) -> None:
self.apple2 = Reference(apple2)
self.apple2_ref = Reference(apple2)
self.gap_energy_motor_converter = gap_energy_motor_converter
self.phase_energy_motor_converter = phase_energy_motor_converter
self.inverse_gap_energy_motor_converter = inverse_gap_energy_motor_converter
Expand All @@ -125,7 +124,7 @@ def __init__(
self.polarisation_setpoint, self._polarisation_setpoint_set = (
soft_signal_r_and_setter(Pol)
)
phase = self.apple2().phase()
phase = self.apple2_ref().phase_ref()
# For unlocked phase axes, use top_inner and btm_outer readbacks to calculate
# derived signal polarisation.
if isinstance(phase, UndulatorPhaseAxes):
Expand All @@ -146,15 +145,15 @@ def __init__(
top_inner=top_inner,
btm_inner=phase.btm_inner.user_readback,
btm_outer=btm_outer,
gap=self.apple2().gap().user_readback,
gap=self.apple2_ref().gap_ref().user_readback,
)
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
self.energy = derived_signal_rw(
raw_to_derived=self._read_energy,
set_derived=self._set_energy,
energy=self._energy,
pol=self.polarisation,
gap=self.apple2().gap().user_readback,
gap=self.apple2_ref().gap_ref().user_readback,
derived_units=units,
)

Expand All @@ -175,7 +174,7 @@ async def _set_motors_from_energy_and_polarisation(
phase = self.phase_energy_motor_converter(value=energy, pol=pol)
apple2_val = self._get_apple2_value(gap, phase, pol)
LOGGER.info(f"Setting polarisation to {pol}, with values: {apple2_val}")
await self.apple2().set(id_motor_values=apple2_val)
await self.apple2_ref().set(id_motor_values=apple2_val)

async def _set_energy(self, energy: float) -> None:
pol = await self._check_and_get_pol_setpoint()
Expand Down
40 changes: 40 additions & 0 deletions src/dodal/devices/insertion_device/apple2_motors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from dataclasses import dataclass

from ophyd_async.core import derived_signal_rw
from ophyd_async.epics.core import epics_signal_rw
from ophyd_async.epics.motor import Motor, MotorMoveLogic

from dodal.log import LOGGER


class MotorStringSetpoint(Motor):
"""Create a float interface for an string-valued setpoint PV Motor.

Undulator motors expose their setpoint PV as a string rather than a numeric
value. This helper creates both the underlying string signal and a derived
float signal that transparently converts between ``float`` values used by
clients and the string representation required by the IOC.
"""

def __init__(self, prefix: str, user_setpoint_str_pv: str, name: str = ""):
super().__init__(prefix, name)
self.user_setpoint_str = epics_signal_rw(str, user_setpoint_str_pv)
self.user_setpoint = derived_signal_rw(
self._read_user_setpoint,
self._set_user_setpoint,
setpoint=self.user_setpoint_str,
)

async def _set_user_setpoint(self, value: float) -> None:
await self.user_setpoint_str.set(str(value))

def _read_user_setpoint(self, setpoint: str) -> float:
return float(setpoint)


@dataclass
class UnstoppableMotorMoveLogic(MotorMoveLogic):
"""Motor move logic for motors that cannot be stopped."""

async def stop(self):
LOGGER.warning(f"Stopping {self.readback.name} is not supported.")
Loading
Loading