Ophyd is the hardware abstraction layer that provides a consistent
interface between the underlying control communication protocol and
bluesky. This is done by
bundling sets of the underlying process variables into hierarchical
devices and exposing a semantic API in terms of control system
primitives. Two terms that will be used throughout are
SignalRepresents an atomic ‘process variable’. This is nominally a ‘scalar’ value and cannot be decomposed any further by layers above
ophyd. In this context an array (waveform) or string would be a scalar because there is no ophyd API to read only part of it.
DeviceHierarchy composed of Signals and other Devices. The components of a Device can be introspected by layers above
ophydand may be decomposed to, ultimately, the underlying Signals.
Put another way, if a hierarchical device is a tree, Signals are the leaves and Devices are the nodes.
In ophyd, we can think of a Device as a tree of sub-devices and eventually the ‘leaf’ nodes which are Signals (and map to 1 or 2 PVs). At the bottom of the tree, each Signal (leaf-node) has 3 names associated with it:
- The PV name it is going to talk to. Typically, this name must be globally unique within the control system you are using. This can lead to them being both verbose and cryptic. From
ophyd’s point of view these strings are taken as given and does not require any particular pattern, scheme, rhyme, or reason in the names.
- The Python attribute name. These are the names of the components of a device and allow attribute-style access to the sub components as
dev.cpt_name. These names are set in the ophyd.Device sub-class definitions. They need to be a valid Python identifiers (which Python enforces) and should be chosen to makes sense to the people directly working with the ophyd instances. They need be unique within a ~ophyd.Device and hence Python ensures that the fully qualified name will be unique within a namespace.
- The ``obj.name`` attribute. This name is the one that will be used in the data returned by ~ophyd.Device.read and will eventually end up in the flowing through bluesky and into databroker to be eventually exposed to the users at analysis times. By default, these names are derived from the Python attribute name of the sub-device and the name of it’s parent, but can be set at runtime. These names should be picked to make scientific sense at analysis time and must be unique among devices that will be used simultaneously.
Uniform High-level Interface¶
All ophyd objects implemented a small set of methods which are used by bluesky plans. It is the responsibility of the ophyd objects to correctly implement these methods in terms of the underlying control system.
The minimum set of methods an object must implement is
||Trigger the device and return status object.|
||Read data from the device.|
||Provide schema and meta-data for
along with three properties:
||name of the device|
||The parent of the ophyd object.|
||Walk parents to find ultimate ancestor (parent’s parent…).|
There are two optional methods which plans may use to ‘enable’ or
‘disable’ a device for data collection. For example, a beam position
monitor maybe in continuous mode when not collecting data but be
stitched to a triggered mode for scanning. By convention
stage did to the state of the underlying
hardware and should return it to the state it was before
||Stage the device for data collection.|
||Unstage the device.|
Two additional optional methods are used to notify devices if,
during a scan, the run is suspended. The semantics of these methods
is coupled to
||Attempt to ‘pause’ the device.|
||Resume a device from a ‘paused’ state.|
Of course, most interesting uses of hardware requires telling it to do
rather than just reading from it! To do that the high-level API has
set method and a corresponding
stop method to halt motion
before it is complete.
set method which returns Status that can be used to tell
when motion is done. It is the responsibility of the ophyd objects
to implement this functionality in terms of the underlying control
system. Thus, from the perspective of the bluesky, a motor, a
temperature controller, a gate valve, and software pseudo-positioner
can all be treated the same.
||Set a value and return a Status object|
In addition to values we will want to read, as ‘data’, or set, as a ‘position’, there tend to be many values associated with the configuration of hardware. This is things like the velocity of a motor, the PID loop parameters of a feedback loop, or the chip temperature of a detector. In general these are measurements that are not directly related to the measurement of interest, but maybe needed for understanding the measured data.
||Configure the device for something during a run|
||Dictionary mapping names to value dicts with keys: value, timestamp|
||Provide schema & meta-data for
There is some hardware where instead of the fine-grained control
read we just want to tell it
“Go!” and check back later when it is done. This is typically done
when there needs to coordinated motion or triggering at rates beyond
what can reasonably done in via EPICS/Python and tend to be called ‘fly scans’.
The flyable interface provides four methods
||Start a flyer|
||Wait for flying to be complete.|
||Provide schema & meta-data from
||Retrieve data from the flyer as proto-events|
Hardware control and data collection is an inherently asynchronous
activity. The many devices on a beamline are (in general) uncoupled
and can move / read independently. This is reflected in the API as
most of the methods in
BlueskyInterface returning Status
objects and in the callback registry at the core of
StatusBase objects are the
bridge between the asynchronous behavior of the underlying control
system and the asynchronous behavior of
The core API of the status objects is a property and a private method:
||Inform the status object that it is done and if it succeeded|
The bluesky side assigns a callback to
status.StatusBase.finished_cb which is triggered when the
status.StatusBase._finished() method is called. The status object
conveys both that the action it ‘done’ and if the action was
successful or not.
- The base class of almost all objects in
- a callback registry
||The base class for all objects in Ophyd|
||Events that can be subscribed to via obj.subscribe|
||Subscribe to events this event_type generates.|
||Remove a subscription|
||Remove a subscription, given the original callback function|
||Run a set of subscription callbacks|
||Remove all subscriptions in an event type|
This registry is used to connect to the underlying events from the
control system and propagate them up to bluesky, either via
~status.StatusBase objects or via direct subscription from the