Positioners#
PVPositioner#
For example, this code defines a CS700 temperature controller. A temperature controller is a kind of positioner, from ophyd’s point of view, where the “position” is the temperature.
from ophyd import PVPositioner, EpicsSignal, EpicsSignalRO
from ophyd import Component as Cpt
# Define a new kind of device.
class CS700TemperatureController(PVPositioner):
setpoint = Cpt(EpicsSignal, 'T-SP')
readback = Cpt(EpicsSignalRO, 'T-I')
done = Cpt(EpicsSignalRO, 'Cmd-Busy')
stop_signal = Cpt(EpicsSignal, 'Cmd-Cmd')
# Create an instance of this new kind of device.
prefix = 'XF:28IDC-ES:1{Env:01}'
cs700 = CS700TemperatureController(prefix, name='cs700')
# When the cs700 has reached the set-point temperature, the 'done' signal
# flips to 0.
cs700.done_value = 0
- class ophyd.pv_positioner.PVPositioner(prefix='', *, limits=None, name=None, read_attrs=None, configuration_attrs=None, parent=None, egu='', **kwargs)#
A Positioner which is controlled using multiple user-defined signals
Keyword arguments are passed through to the base class, Positioner
- Parameters:
- prefixstr, optional
The device prefix used for all sub-positioners. This is optional as it may be desirable to specify full PV names for PVPositioners.
- limits2-element sequence, optional
(low_limit, high_limit)
- namestr
The device name
- egustr, optional
The engineering units (EGU) for the position
- settle_timefloat, optional
The amount of time to wait after moves to report status completion
- timeoutfloat, optional
The default timeout to use for motion requests, in seconds.
- Attributes:
- setpointSignal
The setpoint (request) signal
- readbackSignal or None
The readback PV (e.g., encoder position PV)
- actuateSignal or None
The actuation PV to set when movement is requested
- actuate_valueany, optional
The actuation value, sent to the actuate signal when motion is requested
- stop_signalSignal or None
The stop PV to set when motion should be stopped
- stop_valueany, optional
The value sent to stop_signal when a stop is requested
- doneSignal
A readback value indicating whether motion is finished
- done_valueany, optional
The value that the done pv should be when motion has completed
- put_completebool, optional
If set, the specified PV should allow for asynchronous put completion to indicate motion has finished. If
actuate
is specified, it will be used for put completion. Otherwise, thesetpoint
will be used. See the-c
option fromcaput
for more information.
PVPositionerComparator#
As an example, take a shutter that has a setpoint PV and a readback
PV. The setpoint PV takes only CLOSE_VALVE and OPEN_VALVE commands.
The readback PV goes through the states OPEN -> MOVING -> CLOSED ->
MOVING and back to OPEN. The done_comparator()
method knows that
the movement is done when the readback
argument is not the MOVING
state.
From Bluesky, this device can be moved like any other motor.
from ophyd.pv_positioner import PVPositionerComparator
from ophyd import Component, EpicsSignal, EpicsSignalRO
from enum import IntEnum, unique
from typing import Any
@unique
class SetpointCmd(IntEnum):
CLOSE_VALVE = 1
OPEN_VALVE = 2
@unique
class ReadbackStatus(IntEnum):
UNKNOWN = 0
INVALID = 1
CLOSED = 2
OPEN = 3
MOVING = 4
class Shutter(PVPositionerComparator):
setpoint = Component(EpicsSignal, ":OPEN_CLOSE_CMD")
readback = Component(EpicsSignalRO, ":OPEN_CLOSE_STATUS")
def __init__(self, prefix: str, *, name: str, **kwargs):
kwargs.update({"limits": (SetpointCmd.CLOSE_VALVE,
SetpointCmd.OPEN_VALVE)})
super().__init__(prefix, name=name, **kwargs)
def done_comparator(self, readback:Any, setpoint:Any) -> bool:
return readback != ReadbackStatus.MOVING
- class ophyd.pv_positioner.PVPositionerComparator(prefix: str, *, name: str, **kwargs)#
PV Positioner with a software done signal.
The done state is set by a comparison function defined in the class body. The comparison function takes two arguments, readback and setpoint, returning True if we are considered done or False if we are not.
This class is intended to support
PVPositionerIsClose
, but exists to allow some flexibility if we want to use other metrics for deciding if the PVPositioner is done.Internally, this will subscribe to both the
setpoint
andreadback
signals, updatingdone
as appropriate.- Parameters:
- prefixstr, optional
The device prefix used for all sub-positioners. This is optional as it may be desirable to specify full PV names for PVPositioners.
- limits2-element sequence, optional
(low_limit, high_limit)
- namestr
The device name
- egustr, optional
The engineering units (EGU) for the position
- settle_timefloat, optional
The amount of time to wait after moves to report status completion
- timeoutfloat, optional
The default timeout to use for motion requests, in seconds.
- Attributes:
- setpointSignal
The setpoint (request) signal
- readbackSignal or None
The readback PV (e.g., encoder position PV)
- actuateSignal or None
The actuation PV to set when movement is requested
- actuate_valueany, optional
The actuation value, sent to the actuate signal when motion is requested
- stop_signalSignal or None
The stop PV to set when motion should be stopped
- stop_valueany, optional
The value sent to stop_signal when a stop is requested
- put_completebool, optional
If set, the specified PV should allow for asynchronous put completion to indicate motion has finished. If
actuate
is specified, it will be used for put completion. Otherwise, thesetpoint
will be used. See the-c
option fromcaput
for more information.
- class ophyd.pv_positioner.PVPositionerIsClose(prefix: str, *, name: str, atol: float | None = None, rtol: float | None = None, **kwargs)#
PV Positioner that updates done state based on np.isclose.
Effectively, this will treat our move as complete if the readback is sufficiently close to the setpoint. This is generically helpful for PV positioners that don’t have a
done
signal built into the hardware.The arguments atol and rtol can be set as class attributes or passed as initialization arguments.
atol is a measure of absolute tolerance. If atol is 0.1, then you’d be able to be up to 0.1 units away and still count as done. This is typically the most useful parameter for calibrating done tolerance.
rtol is a measure of relative tolerance. If rtol is 0.1, then you’d be able to deviate from the goal position by up to 10% of its value. This is useful for small quantities. For example, defining an atol for a positioner that ranges from 1e-8 to 2e-8 could be somewhat awkward.
- Parameters:
- prefixstr, optional
The device prefix used for all sub-positioners. This is optional as it may be desirable to specify full PV names for PVPositioners.
- limits2-element sequence, optional
(low_limit, high_limit)
- namestr
The device name
- egustr, optional
The engineering units (EGU) for the position
- settle_timefloat, optional
The amount of time to wait after moves to report status completion
- timeoutfloat, optional
The default timeout to use for motion requests, in seconds.
- atolfloat, optional
A measure of absolute tolerance. If atol is 0.1, then you’d be able to be up to 0.1 units away and still count as done.
- rtolfloat, optional
A measure of relative tolerance. If rtol is 0.1, then you’d be able to deviate from the goal position by up to 10% of its value
- Attributes:
- setpointSignal
The setpoint (request) signal
- readbackSignal or None
The readback PV (e.g., encoder position PV)
- actuateSignal or None
The actuation PV to set when movement is requested
- actuate_valueany, optional
The actuation value, sent to the actuate signal when motion is requested
- stop_signalSignal or None
The stop PV to set when motion should be stopped
- stop_valueany, optional
The value sent to stop_signal when a stop is requested
- put_completebool, optional
If set, the specified PV should allow for asynchronous put completion to indicate motion has finished. If
actuate
is specified, it will be used for put completion. Otherwise, thesetpoint
will be used. See the-c
option fromcaput
for more information.- atolfloat, optional
A measure of absolute tolerance. If atol is 0.1, then you’d be able to be up to 0.1 units away and still count as done.
- rtolfloat, optional
A measure of relative tolerance. If rtol is 0.1, then you’d be able to deviate from the goal position by up to 10% of its value
PseudoPositioner#
An ophyd PseudoPositioner
relates one or more pseudo (virtual) axes to one
or more real (physical) axes via forward and inverse calculations. To define
such a PseudoPositioner, one must subclass from PseudoPositioner:
from ophyd import (PseudoPositioner, PseudoSingle, EpicsMotor)
from ophyd import (Component as Cpt)
from ophyd.pseudopos import (pseudo_position_argument,
real_position_argument)
class Pseudo3x3(PseudoPositioner):
# The pseudo positioner axes:
px = Cpt(PseudoSingle, limits=(-10, 10))
py = Cpt(PseudoSingle, limits=(-10, 10))
pz = Cpt(PseudoSingle)
# The real (or physical) positioners:
rx = Cpt(EpicsMotor, 'XF:31IDA-OP{Tbl-Ax:X1}Mtr')
ry = Cpt(EpicsMotor, 'XF:31IDA-OP{Tbl-Ax:X2}Mtr')
rz = Cpt(EpicsMotor, 'XF:31IDA-OP{Tbl-Ax:X3}Mtr')
@pseudo_position_argument
def forward(self, pseudo_pos):
'''Run a forward (pseudo -> real) calculation'''
return self.RealPosition(rx=-pseudo_pos.px,
ry=-pseudo_pos.py,
rz=-pseudo_pos.pz)
@real_position_argument
def inverse(self, real_pos):
'''Run an inverse (real -> pseudo) calculation'''
return self.PseudoPosition(px=-real_pos.rx,
py=-real_pos.ry,
pz=-real_pos.rz)
Pseudo3x3
above is a pseudo positioner with 3 pseudo axes and 3 real axes.
The pseudo axes are defined in order as (px, py, pz). Similarly, the real
positioners are (rx, ry, rz).
There is no restriction that the real axes must be tied to physical hardware.
A physical axis could just as well be a SoftPositioner
, or any subclass of
PositionerBase
(with the sole exception of PseudoSingle
).
The forward calculation says that, for any given pseudo position, the real motors should move to the opposite position. For example, for a pseudo position of (px=1, py=2, pz=3), the corresponding real position would be (rx=-1, ry=-2, rz=-3). The inverse calculation is similar, in going from a real position to a pseudo position.
The two decorators @real_position_argument
and
@pseudo_position_argument
are used here for convenience so that one can
call these functions in a variety of ways, all of which generate a correct
PseudoPosition tuple as the first argument to your calculation method.
Positions can be specified in the following ways:
As positional arguments:
pseudo.forward(px, py, pz)
As a sequence or PseudoPosition/RealPosition:
pseudo.forward((px, py, pz))
pseudo.forward(pseudo.PseudoPosition(px, py, pz))
As kwargs:
pseudo.forward(px=1, py=2, pz=3)
move
is decorated like this on PseudoPositioner, meaning you can also call
it with this syntax.
- class ophyd.pseudopos.PseudoSingle(prefix='', *, limits=None, egu='', parent=None, name=None, source='computed', target_initial_position=False, **kwargs)#
A single axis of a PseudoPositioner
This should not be instantiated on its own, but rather used as a Component in a PseudoPositioner subclass.
- Parameters:
- prefixstr, optional
The PV prefix, for compatibility with the Device hierarchy
- limits(low_limit, high_limit)
User-defined limits for this pseudo axis.
- egustr, optional
The engineering units (EGU) for the position
- parentPseudoPositioner instance
The instance of the parent PseudoPositioner
- namestr, optional
The name of the positioner
- sourcestr, optional
Metadata indicating the source of this positioner’s position. Defaults to ‘computed’
- target_initial_positionbool, optional
Use initial inverse-calculated position as the default setpoint/target for this axis
- settle_timefloat, optional
The amount of time to wait after moves to report status completion
- timeoutfloat, optional
The default timeout to use for motion requests, in seconds.
- class ophyd.pseudopos.PseudoPositioner(prefix='', *, concurrent=True, read_attrs=None, configuration_attrs=None, name, egu='', auto_target=True, **kwargs)#
A pseudo positioner which can be comprised of multiple positioners
- Parameters:
- prefixstr
The PV prefix for all components of the device
- concurrentbool, optional
If set, all real motors will be moved concurrently. If not, they will be moved in order of how they were defined initially
- read_attrssequence of attribute names
the components to include in a normal reading (i.e., in
read()
)- configuration_attrssequence of attribute names
the components to be read less often (i.e., in
read_configuration()
) and to adjust viaconfigure()
- namestr, optional
The name of the device
- egustr, optional
The user-defined engineering units for the whole PseudoPositioner
- auto_targetbool, optional
Automatically set the target position of PseudoSingle devices when moving to a single PseudoPosition
- parentinstance or None
The instance of the parent device, if applicable
- settle_timefloat, optional
The amount of time to wait after moves to report status completion
- timeoutfloat, optional
The default timeout to use for motion requests, in seconds.
SoftPositioner#
A SoftPositioner
is a positioner which has no corresponding physical motor.
On its own, it is most useful for debugging scanning logic when moving physical
motors is either undesirable or not possible.
Used as-is, a SoftPositioner
will “move” to the requested position
immediately.
PseudoSingle
and PseudoPositioner
, for example, are implemented as
heavily customized SoftPositioner
subclasses.
- class ophyd.positioner.PositionerBase(*, name=None, parent=None, settle_time=0.0, timeout=None, **kwargs)#
The positioner base class
Subclass from this to implement your own positioners.
Note
Subclasses should add an additional ‘wait’ keyword argument on the move method. The
MoveStatus
object returned fromPositionerBase
can then be waited on after the subclass finishes the motion configuration.
- class ophyd.positioner.SoftPositioner(*, egu='', limits=None, source='computed', init_pos=None, **kwargs)#
A positioner which does not communicate with any hardware
SoftPositioner ‘moves’ immediately to the target position when commanded to do so.
- Parameters:
- limits(low_limit, high_limit)
Soft limits to use
- egustr, optional
Engineering units (EGU) for a position
- sourcestr, optional
Metadata indicating the source of this positioner’s position. Defaults to ‘computed’
- init_posfloat, optional
Create the positioner with this starting position. Defaults to
None
.
from ophyd import SoftPositioner
my_positioner = SoftPositioner(name='my_positioner')