Device and Component¶
Usage¶
The core class of ophyd
is Device
which encodes the
nodes of the hierarchical structure of the device and provides much of
core API.
|
Base class for device objects |
The base Device
is not particularly useful on it’s own, it
must be sub-classed to provide it with components to do something
with.
Creating a custom device is as simple as
from ophyd import Device, EpicsMotor
from ophyd import Component as Cpt
class StageXY(Device):
x = Cpt(EpicsMotor, ':X')
y = Cpt(EpicsMotor, ':Y')
stage = StageXY('STAGE_PV', name='stage')
You can then use stage
as an input to any plan as a detector and
stage.x
and stage.y
as independent motors.
A Robot¶
A slightly more complex example is to control a simple sample loading robot.
from ophyd import Device, EpicsSignal, EpicsSignalRO
from ophyd import Component as Cpt
from ophyd.utils import set_and_wait
class Robot(Device):
sample_number = Cpt(EpicsSignal, 'ID:Tgt-SP')
load_cmd = Cpt(EpicsSignal, 'Cmd:Load-Cmd.PROC')
unload_cmd = Cpt(EpicsSignal, 'Cmd:Unload-Cmd.PROC')
execute_cmd = Cpt(EpicsSignal, 'Cmd:Exec-Cmd')
status = Cpt(EpicsSignalRO, 'Sts-Sts')
my_robot = Robot('PV_PREFIX:', name='my_robot',
read_attrs=['sample_number', 'status'])
Which creates an instance my_robot
with 5 children
python attribute
PV name
in
read()
my_robot.sample_number
‘PV_PREFIX:ID:Tgt-SP’
Y
my_robot.load_cmd
‘PV_PREFIX:CMD:Load-Cmd.PROC’
N
my_robot.unload_cmd
‘PV_PREFIX:CMD:Unload-Cmd.PROC’
N
my_robot.execute_cmd
‘PV_PREFIX:CMD:Exec-Cmd’
N
my_robot.status
‘PV_PREFIX:Sts-Sts’
Y
only 2 of which will be included when reading from the robot.
You could now use this device in a scan like
import bluesky.plans as bp
def load_sample(robot, sample):
yield from bp.mv(robot.sample_number, sample)
yield from bp.mv(robot.load_cmd, 1)
yield from bp.mv(robot.execute_cmd, 1)
def unload_sample(robot):
yield from bp.mv(robot.unload_cmd, 1)
yield from bp.mv(robot.execute_cmd, 1)
def robot_plan(list_of_samples):
for sample in list_of_samples:
# load the sample
yield from load_sample(my_robot, sample)
# take a measurement
yield from bp.count([det], md={'sample': sample})
# unload the sample
yield from unload_sample(my_robot)
and from the command line
RE(robot_plan([1, 2. 6]))
These classes were co-developed with bluesky
and are the
reference implementation of a hardware abstraction layer for
bluesky
. However, these are closely tied to EPICS and make
some assumptions about the PV naming based on NSLS-II’s naming scheme.
Despite attempting generality, it is likely that as ophyd
and
bluesky
are used at other facilities (and when ophyd
is
adapted for a different control system) we will discover some latent
NSLS-II-isms that should be corrected (or at least acknowledged and
documented).
Device
¶
Device
adds a number of additional attributes beyond the
required bluesky
API and what is inherited from OphydObj
for run-time configuration
Attribute
Description
read_attrs
Names of components for
read()
See Trigger, Read and Describe
configuration_attrs
Names of components for
read_configuration()
. See Configuration and Friends
stage_sigs
Signals to be set during Stage and Unstage
hints
Names of components as suggestions for handling in bluesky callbacks.
and static information about the object
Attribute
Description
prefix
‘base’ of PV name, used when building components
component_names
List of the names components on this device. Direct children only
trigger_signals
Signals for use in Implicit Triggering (provisional)
Device
also has two class-level attributes to control the default contents of
read_attrs
and configuration_attrs
.
Attribute
Description
_default_read_attrs
The default contents of
read_attrs
if a subset of all available children.An iterable or None. If None defaults to all children
A
tuple
is recommended.
_default_configuration_attrs
The default contents of
configuration_attrs
An iterable or None. If None defaults to
[]
A
tuple
is recommended.
hints
¶
The hints
attribute is a dictionary that provides information about an
ophyd
device to bluesky
callbacks that advise how that device
should be handled by the callback. While it could be used for many purposes,
its first use has been to direct the selection of the relevant axes and signals
to use when plotting data from an event stream.
There are two different locations where the hints
dictionary is created.
1. During the specification of an ophyd Device
1. Configuration of the start
document by a bluesky
plan
The hints
dictionary has well-known keys.
Key |
Description of value |
---|---|
|
signal names to be used for a plot as dependent axes |
|
signal names to be used for a plot as independent axes |
|
advises when to prefer |
The hints
dictionary may also have custom keys used by the custom support.
example using the ad hoc
vis
key in the creation of anophyd
detector Device:self.hints = {'vis': 'placeholder'}
then look for this key in the custom
bluesky
callback:assert doc['hints']['det'] == {'vis': 'placeholder'}
hints["fields"]
¶
fields
is a list of ophyd object name(s) to be used as dependent axes for
visualization callbacks. The object name(s) must appear in the dictionary
returned by the device’s read()
method.
Examples:
quadem.hints == {'fields': ['quadem_current1_mean_value']}
sca.hints == {'fields': [sca.channels.name]}
To ensure internal consistency, the hints
attribute of any
Signal
or Device
cannot be set directly.
1 Instead of:
camera.hints = {'fields': [camera.stats1.total.name,
camera.stats2.total.name]}
use the kind
kind attribute.
from ophyd import Kind
camera.stats1.total.kind = Kind.hinted
camera.stats2.total.kind = Kind.hinted
or, as a convenient shortcut
camera.stats1.total.kind = 'hinted'
camera.stats2.total.kind = 'hinted'
- 1
starting with ophyd v1.2.0
hints["dimensions"]
¶
Defines the ophyd signal names to be used as independent axes for visualization.
The syntax is (list of field names, stream name) where the list of field
names is as above and the stream name is usually primary
.
All the signals must be available in the named stream.
hints["dimensions"]
is used by a bluesky
plan
to prepare a dimensions
attribute that is placed
in the start document. It is this dimensions
attribute
that identifies the independent axes for visualization callbacks.
The plan can use or override what it finds in hints["dimensions"]
.
Examples:
dimensions = [(motor.hints['fields'], 'primary')]
dimensions = [(('time'), 'primary')]
For now, bluesky
can only handle when all the dimensions belong
to the same stream. To generalize, we would need to resample
and we are not going to handle that yet.
hints["gridding"]
¶
This key is used for mesh and grid scans. When present, it can take these values:
rectilinear
or rectilinear_nonsequential
.
In the Best Effort Callback from bluesky
, if hints["gridding"]
exists and
is "rectilinear"
, then use LiveGrid, otherwise use LivePlot.
Component
¶
The Component
class is a python descriptor which overrides
the behavior on attribute access. This allows us to use a declarative style to
define the software representation of the hardware. The best way to
understand is through an example:
class Foo(Device):
bar = Component(EpicsSignal, ':bar', string=True)
which means “When a Foo
instance is created give it a bar
attribute
which is an instance of EpicsSignal
and use the extra args
and kwargs when creating it”. It is a declaration of what you want
and it is the responsibility of ophyd
to make it happen.
There are three classes
|
A descriptor representing a device component (or signal) |
|
A Component which takes a dynamic format string |
|
An Device component that dynamically creates an ophyd Device |
Trigger, Read and Describe¶
The trigger()
method is responsible for
implementing ‘trigger’ or ‘acquire’ functionality of the Device.
The read()
method is responsible for
for returning the data from the Device.
The describe()
method is responsible for
providing schema and meta-data for the read() method.
Configuration and Friends¶
Stage and Unstage¶
When a Device d
is used in scan, it is “staged” and “unstaged.” Think of
this as “setup” and “cleanup”. That is, before a device is triggered, read, or
moved, the scan is expected to call d.stage()
. And, at the end of scan,
d.unstage()
is called. (Whenever possible, unstaging is performed even if
the scan is aborted or fails due to an error.)
The staging process is a “hook” for preparing a device for use. To add
custom staging logic to a Device, subclass it and override stage
and/or
unstage
like so.
class MyMotor(EpicsMotor):
def stage(self):
print('I am staging.')
super().stage()
def unstage(self):
print('I am unstaging.')
super().unstage()
It is crucial to call super()
, as above, so that any built-in staging
behavior is not overridden.
A common use for staging is to set certain signals to certain values for
a scan and then set them back at the end. For example, a detector device
might turn on “capture mode” at the beginning of the scan and then flip it
back off (or back to its original setting, whatever that was) at the end.
For this, ophyd provides a convenience, stage_sigs
— a dictionary
mapping signals to desired values. The device reads the initial values
of these signals, stashes them, changes them to the desired value, and then
restore the initial value when the device is unstaged. It is best to
customize stage_sigs
in the device’s __init__
method, like so:
class MyMotor(EpicsMotor):
def __init__(*args, **kwargs):
super().__init__(*args, **kwargs)
self.stage_sigs[self.user_offset] = 5
When a MyMotor
device is staged, its user_offset
value will be set
to 5. When it is unstaged, it will be set back to whatever value it had
right before it was staged.
Implicit Triggering¶
Count Time¶
Low level API¶
If the device is connected. |
|
Wait for signals to connect |
|
Yields all of the instantiated signals in a device hierarchy |
|
Get the value of all components in the device |
|
Put a value to all components of the device |
|
The device tuple type associated with an Device class |