Architecture¶
Hardware abstraction¶
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
Signal
Represents 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.Device
Hierarchy composed of Signals and other Devices. The components of a Device can be introspected by layers above
ophyd
and 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.
Names¶
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.
Read-able Interface¶
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 unstage
‘undoes’ whatever stage
did to the state of the underlying
hardware and should return it to the state it was before stage
was
called.
|
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 RunEngine
.
|
Attempt to ‘pause’ the device. |
|
Resume a device from a ‘paused’ state. |
Set-able Interface¶
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
the set
method and a corresponding stop
method to halt motion
before it is complete.
The 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 |
|
Stops motion. |
Configuration¶
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 |
Fly-able Interface¶
There is some hardware where instead of the fine-grained control
provided by set
, trigger
, and 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 |
The expected sequencing of the commands is
kickoff
and waitcomplete
and waitcollect
Optionally, devices my implement “partial collection” so that they can be incrementally collected during acquisition. While this may not be technically possible in every situation, it can be used to get partial results and allow the fly scan to look more like a step scan from the point of view of the data consumers.
kickoff
and wait0 or more calls to
collect
complete
and waitcollect
import bluesky.preprocessors as bpp
import bluesky.plan_stubs as bps
from bluesky import RunEngine
from bluesky.callbacks.best_effort import BestEffortCallback
from ophyd.sim import SynAxis, SynGauss, MockFlyer
motor = SynAxis(name="motor", labels={"motors"}, value=0.0)
det = SynGauss("det", motor, "motor", center=0, Imax=1, sigma=1, labels={"detectors"})
flyer1 = MockFlyer("primary", det, motor, -3, 5, 200)
motor.delay = .1
def single_collect(flyer, *, md=None):
_md = {}
_md.update(md or {})
@bpp.run_decorator(md=_md)
def single_collect(flyer):
yield from bps.kickoff(flyer, wait=True)
yield from bps.complete(flyer, wait=True)
yield from bps.collect(flyer)
return (yield from single_collect(flyer))
def multi_collect(flyer, *, md=None):
_md = {}
_md.update(md or {})
@bpp.run_decorator(md=_md)
def multi_collect(flyer):
yield from bps.kickoff(flyer, wait=True)
st = yield from bps.complete(flyer)
while st is not None and not st.done:
yield from bps.collect(flyer, stream=True)
yield from bps.sleep(1)
yield from bps.collect(flyer, stream=True)
return (yield from multi_collect(flyer))
RE = RunEngine()
bec = BestEffortCallback()
# RE(multi_collect(flyer1), bec)
# RE(single_collect(flyer1), bec)
Asynchronous status¶
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
OphydObject
. The StatusBase
objects are the
bridge between the asynchronous behavior of the underlying control
system and the asynchronous behavior of
RunEngine
.
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.
Callbacks¶
- The base class of almost all objects in
ophyd
isOphydObject
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
RunEngine
.