-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #243 from DiamondLightSource/hyperion_986_set_ener…
…gy_apply_lookup_table_value_for_pitch_roll Create devices needed for aligning the DCM and mirrors when changing energy
- Loading branch information
Showing
26 changed files
with
538 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
from enum import Enum | ||
from typing import Any | ||
|
||
from ophyd import Component, Device, EpicsMotor, EpicsSignal | ||
from ophyd.status import Status, StatusBase | ||
|
||
from dodal.log import LOGGER | ||
|
||
VOLTAGE_POLLING_DELAY_S = 0.5 | ||
|
||
# The default timeout is 60 seconds as voltage slew rate is typically ~2V/s | ||
DEFAULT_SETTLE_TIME_S = 60 | ||
|
||
DEMAND_ACCEPTED_OK = 1 | ||
|
||
|
||
class MirrorStripe(Enum): | ||
RHODIUM = "Rhodium" | ||
BARE = "Bare" | ||
PLATINUM = "Platinum" | ||
|
||
|
||
class MirrorVoltageDevice(Device): | ||
"""Abstract the bimorph mirror voltage PVs into a single device that can be set asynchronously and returns when | ||
the demanded voltage setpoint is accepted, without blocking the caller as this process can take significant time. | ||
""" | ||
|
||
_actual_v: EpicsSignal = Component(EpicsSignal, "R") | ||
_setpoint_v: EpicsSignal = Component(EpicsSignal, "D") | ||
_demand_accepted: EpicsSignal = Component(EpicsSignal, "DSEV") | ||
|
||
def set(self, value, *args, **kwargs) -> StatusBase: | ||
"""Combine the following operations into a single set: | ||
1. apply the value to the setpoint PV | ||
2. Return to the caller with a Status future | ||
3. Wait until demand is accepted | ||
4. When either demand is accepted or DEFAULT_SETTLE_TIME expires, signal the result on the Status | ||
""" | ||
|
||
setpoint_v = self._setpoint_v | ||
demand_accepted = self._demand_accepted | ||
|
||
if setpoint_v.get() == value: | ||
LOGGER.debug(f"{setpoint_v.name} already at {value} - skipping set") | ||
return Status(success=True, done=True) | ||
|
||
if demand_accepted.get() != DEMAND_ACCEPTED_OK: | ||
raise AssertionError( | ||
f"Attempted to set {setpoint_v.name} when demand is not accepted." | ||
) | ||
|
||
LOGGER.debug(f"setting {setpoint_v.name} to {value}") | ||
demand_accepted_status = Status(self, DEFAULT_SETTLE_TIME_S) | ||
|
||
subscription: dict[str, Any] = {"handle": None} | ||
|
||
def demand_check_callback(old_value, value, **kwargs): | ||
LOGGER.debug(f"Got event old={old_value} new={value}") | ||
if old_value != DEMAND_ACCEPTED_OK and value == DEMAND_ACCEPTED_OK: | ||
LOGGER.debug(f"Demand accepted for {setpoint_v.name}") | ||
subs_handle = subscription.pop("handle", None) | ||
if subs_handle is None: | ||
raise AssertionError("Demand accepted before set attempted") | ||
demand_accepted.unsubscribe(subs_handle) | ||
|
||
demand_accepted_status.set_finished() | ||
# else timeout handled by parent demand_accepted_status | ||
|
||
subscription["handle"] = demand_accepted.subscribe(demand_check_callback) | ||
setpoint_status = setpoint_v.set(value) | ||
status = setpoint_status & demand_accepted_status | ||
return status | ||
|
||
|
||
class VFMMirrorVoltages(Device): | ||
def __init__(self, *args, daq_configuration_path: str, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.voltage_lookup_table_path = ( | ||
daq_configuration_path + "/json/mirrorFocus.json" | ||
) | ||
|
||
_channel14_voltage_device = Component(MirrorVoltageDevice, "BM:V14") | ||
_channel15_voltage_device = Component(MirrorVoltageDevice, "BM:V15") | ||
_channel16_voltage_device = Component(MirrorVoltageDevice, "BM:V16") | ||
_channel17_voltage_device = Component(MirrorVoltageDevice, "BM:V17") | ||
_channel18_voltage_device = Component(MirrorVoltageDevice, "BM:V18") | ||
_channel19_voltage_device = Component(MirrorVoltageDevice, "BM:V19") | ||
_channel20_voltage_device = Component(MirrorVoltageDevice, "BM:V20") | ||
_channel21_voltage_device = Component(MirrorVoltageDevice, "BM:V21") | ||
|
||
@property | ||
def voltage_channels(self) -> list[MirrorVoltageDevice]: | ||
return [ | ||
self._channel14_voltage_device, | ||
self._channel15_voltage_device, | ||
self._channel16_voltage_device, | ||
self._channel17_voltage_device, | ||
self._channel18_voltage_device, | ||
self._channel19_voltage_device, | ||
self._channel20_voltage_device, | ||
self._channel21_voltage_device, | ||
] | ||
|
||
|
||
class FocusingMirror(Device): | ||
"""Focusing Mirror""" | ||
|
||
def __init__(self, bragg_to_lat_lut_path, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.bragg_to_lat_lookup_table_path = bragg_to_lat_lut_path | ||
|
||
yaw_mrad: EpicsMotor = Component(EpicsMotor, "YAW") | ||
pitch_mrad: EpicsMotor = Component(EpicsMotor, "PITCH") | ||
fine_pitch_mm: EpicsMotor = Component(EpicsMotor, "FPMTR") | ||
roll_mrad: EpicsMotor = Component(EpicsMotor, "ROLL") | ||
vert_mm: EpicsMotor = Component(EpicsMotor, "VERT") | ||
lat_mm: EpicsMotor = Component(EpicsMotor, "LAT") | ||
jack1_mm: EpicsMotor = Component(EpicsMotor, "Y1") | ||
jack2_mm: EpicsMotor = Component(EpicsMotor, "Y2") | ||
jack3_mm: EpicsMotor = Component(EpicsMotor, "Y3") | ||
translation1_mm: EpicsMotor = Component(EpicsMotor, "X1") | ||
translation2_mm: EpicsMotor = Component(EpicsMotor, "X2") | ||
|
||
stripe: EpicsSignal = Component(EpicsSignal, "STRP:DVAL", string=True) | ||
# apply the current set stripe setting | ||
apply_stripe: EpicsSignal = Component(EpicsSignal, "CHANGE.PROC") | ||
|
||
def energy_to_stripe(self, energy_kev): | ||
# In future, this should be configurable per-mirror | ||
if energy_kev < 7: | ||
return MirrorStripe.BARE | ||
else: | ||
return MirrorStripe.RHODIUM |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
""" | ||
All the methods in this module return a bluesky plan generator that adjusts a value | ||
according to some criteria either via feedback, preset positions, lookup tables etc. | ||
""" | ||
from typing import Callable, Generator | ||
|
||
from bluesky import plan_stubs as bps | ||
from bluesky.run_engine import Msg | ||
from ophyd.epics_motor import EpicsMotor | ||
|
||
from dodal.log import LOGGER | ||
|
||
|
||
def lookup_table_adjuster( | ||
lookup_table: Callable[[float], float], output_device: EpicsMotor, input | ||
): | ||
"""Returns a callable that adjusts a value according to a lookup table""" | ||
|
||
def adjust(group=None) -> Generator[Msg, None, None]: | ||
setpoint = lookup_table(input) | ||
LOGGER.info(f"lookup_table_adjuster setting {output_device.name} to {setpoint}") | ||
yield from bps.abs_set(output_device, setpoint, group=group) | ||
|
||
return adjust |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
""" | ||
All the public methods in this module return a lookup table of some kind that | ||
converts the source value s to a target value t for different values of s. | ||
""" | ||
from collections.abc import Sequence | ||
from typing import Callable | ||
|
||
import numpy as np | ||
from numpy import interp, loadtxt | ||
|
||
from dodal.log import LOGGER | ||
|
||
|
||
def linear_interpolation_lut(filename: str) -> Callable[[float], float]: | ||
"""Returns a callable that converts values by linear interpolation of lookup table values""" | ||
LOGGER.info(f"Using lookup table {filename}") | ||
s_and_t_vals = zip(*loadtxt(filename, comments=["#", "Units"])) | ||
|
||
s_values: Sequence | ||
t_values: Sequence | ||
s_values, t_values = s_and_t_vals | ||
|
||
# numpy interp expects x-values to be increasing | ||
if not np.all(np.diff(s_values) > 0): | ||
LOGGER.info( | ||
f"Configuration file {filename} values are not ascending, trying reverse order..." | ||
) | ||
s_values = list(reversed(s_values)) | ||
t_values = list(reversed(t_values)) | ||
if not np.all(np.diff(s_values) > 0): | ||
raise AssertionError( | ||
f"Configuration file {filename} lookup table does not monotonically increase or decrease." | ||
) | ||
|
||
def s_to_t2(s: float) -> float: | ||
if s < s_values[0] or s > s_values[len(s_values) - 1]: | ||
raise ValueError( | ||
f"Lookup table does not support extrapolation from file {filename}, s={s}" | ||
) | ||
return float(interp(s, s_values, t_values)) | ||
|
||
return s_to_t2 |
Oops, something went wrong.