How Bluesky Interfaces with Hardware¶
Overview¶
Bluesky interacts with hardware through a high-level abstraction, leaving the low-level details of communication as a separate concern. In bluesky’s view, all devices are in a sense “detectors,” in that they can be read. A subset of these devices are “positioners” that can also be set (i.e., written to or moved).
In short, each device is represented by a Python object that has attributes and methods with certain established names. We have taken pains to make this interface as slim as possible, while still being general enough to address every kind of hardware we have encountered.
Specification¶
Status object¶
The interface of a “status” object, which the RunEngine
uses to
asynchronously monitor the compeletion of having triggered or set a device.
-
Status:
-
done
¶ boolean
-
success
¶ boolean
If
success
isFalse
when the Status is marked done, this is taken to mean, “We have given up.” For example, “The motor is stuck and will never get where it is going.” AFailedStatus
exception will be raised inside the RunEngine.-
finished_cb
¶ a callback function that
Status
will call when it is marked done.
It may be that
Status
is done before a function has been attached tofinished_cb
. In that case, the function should be called as soon as it is attached.-
watch
(func)¶ Subscribe to notifications about progress. Useful for progress bars.
Parameters
- funccallable
Expected to accept the keyword arguments:
name
current
initial
target
unit
precision
fraction
time_elapsed
time_remaining
Any given call to
func
may only include a subset of these parameters, depending on what the status object knows about its own progress.
-
Readable Device¶
The interface of a readable device:
-
class
ReadableDevice
¶ -
name
¶ a human-readable string identifying the device
-
parent
¶ None
, or a reference to a parent deviceSee the
stage
method below for the operational signifance ofparent
.
-
read
()¶ Return an OrderedDict mapping field name(s) to values and timestamps. The field names must be strings. The values can be any JSON-encodable type or a numpy array, which the RunEngine will convert to (nested) lsits. The timestamps should be UNIX time (seconds since 1970).
Example return value:
OrderedDict(('channel1', {'value': 5, 'timestamp': 1472493713.271991}), ('channel2', {'value': 16, 'timestamp': 1472493713.539238}))
-
describe
()¶ Return an OrderedDict with exactly the same keys as the
read
method, here mapped to metadata about each field.Example return value:
OrderedDict(('channel1', {'source': 'XF23-ID:SOME_PV_NAME', 'dtype': 'number', 'shape': []}), ('channel2', {'source': 'XF23-ID:SOME_PV_NAME', 'dtype': 'number', 'shape': []}))
We refer to each entry as a “data key.” These fields are required:
source (a descriptive string — e.g., an EPICS Process Variable)
dtype: one of the JSON data types: {‘number’, ‘string’, ‘array’}
shape: list of integers (dimension sizes) — e.g.,
[5, 5]
for a 5x5 array. Use empty list[]
to indicate a scalar.
Optional additional fields (precision, units, etc.) are allowed. The optional field
external
should be used to provide information about references to externally-stored data, such as large image arrays.
-
trigger
()¶ Return a
Status
that is marked done when the device is done triggering.If the device does not need to be triggered, simply return a
Status
that is marked done immediately.
-
read_configuration
()¶ Same API as
read
but for slow-changing fields related to configuration (e.g., exposure time). These will typically be read only once per run.Of course, for simple cases, you can effectively omit this complexity by returning an empty dictionary.
-
describe_configuration
()¶ Same API as
describe
, but corresponding to the keys inread_configuration
.
-
hints
¶ A dictionary of suggestions for best-effort visualization and processing. This does not affect what data is read or saved; it is only a suggestion to enable automated tools to provide helpful information with minimal guidance from the user. See Hints.
-
configure
(*args, **kwargs)¶ This can change the device’s configuration in an arbitrary way. When the RunEngine calls this method, it also emits a fresh Event Descriptor because it assumes that the configuration in the previous Event Descriptor might no longer be valid.
Returns a tuple of the old result of
read_configuration()
and the new result ofread_configuration()
.
This concludes the required API. The following are optional.
-
stage
()¶ An optional hook for “setting up” the device for acquisition.
It should return a list of devices including itself and any other devices that are staged as a result of staging this one. (The
parent
attribute expresses this relationship: a device should be staged/unstaged whenever its parent is staged/unstaged.)
-
unstage
()¶ A hook for “cleaning up” the device after acquisition.
It should return a list of devices including itself and any other devices that are unstaged as a result of unstaging this one.
-
subscribe
(function)¶ Optional, needed only if the device will be monitored.
When the device has a new value ready, it should call
function
asynchronously in a separate thread.
-
clear_sub
(function)¶ Remove a subscription. (See
subscribe
, above.)
-
pause
()¶ An optional hook to do some device-specific work when the RunEngine pauses.
-
resume
()¶ An optional hook to do some device-specific work when the RunEngine resumes after a pause.
-
Movable (or “Settable”) Device¶
The interface of a movable device extends the interface of a readable device
with the following additional methods and attributes. The utility function
bluesky.utils.is_movable()
can be used to check if an object meets the
expected interface of a Movable.
-
MovableDevice:
-
set
(*args, **kwargs)¶ Return a
Status
that is marked done when the device is done moving. This is the only required method that the Movable interace adds to the Readable one.
-
stop
(success=True)¶ Safely stop a device that may or may not be in motion. The argument
success
is a boolean. Whensuccess
is true, bluesky is stopping the device as planned and the device should stop “normally”. Whensuccess
is false, something has gone wrong and the device may wish to take defensive action to make itself safe.Optional: devices that cannot be stopped should not implement this method.
-
check_value
(*args, **kwargs)¶ This should accept the same arguments as
set
. It should raise an Exception if the argument represent an illegal setting — e.g. a position that would move a motor outside its limits or a temperature controller outside of its settable range.Optional: If this method is not present, simulators that check limits such as
bluesky.simulators.check_limits()
may issue a warning but should assume that all values are legal.
-
position
¶ A heuristic that describes the current position of a device as a single scalar, as opposed to the potentially multi-valued description provided by
read()
.Optional: bluesky itself does not use the position attribute, but other parts of the ecosystem might. Developers are encouraged to implement this attribute where possible.
-
“Flyer” Interface¶
For context on what we mean by “flyer”, refer to the section on :doc:`async`.
The interface of a “flyable” device is separate from the interface of a readable or settable device, though there is some overlap.
-
FlyableDevice:
-
kickoff
()¶ Begin acculumating data. Return a
Status
and mark it done when acqusition has begun.
-
complete
()¶ Return a
Status
and mark it done when acquisition has completed.
-
collect
()¶ Yield dictionaries that are partial Event documents. They should contain the keys ‘time’, ‘data’, and ‘timestamps’. A ‘uid’ is added by the RunEngine.
-
describe_collect
()¶ This is like
describe()
on readable devices, but with an extra layer of nesting. Since a flyer can potentially return more than one event stream, this is a dict of stream names (strings) mapped to adescribe()
-type output for each.
The remaining methods and attributes match ReadableDevice.
-
configure
(*args, **kwargs)¶ same as for a readable device
-
read_configuration
()¶ same as for a readable device
-
describe_configuration
()¶ same as for a readable device
-
name
¶ same as for a readable device
-
parent
¶ same as for a readable device
-
stage
()¶ optional, same as for a readable device
-
unstage
()¶ optional, same as for a readable device
-
pause
()¶ optional, same as for a readable device
-
resume
()¶ optional, same as for a readable device
-
Implementations¶
Real Hardware¶
The ophyd package implements this interface for a wide variety of hardware, communicating using EPICS via the Python bindings pyepics.Other control systems (Tango, LabView, etc.) could be integrated with bluesky in the future by implementing this same interface.
Simulated Hardware¶
A toy “test” implementation the interface is included in the
ophyd.sim
module. These implementations act as simulated hardware,
and we use them extensively in examples, demos, and the test suite. They can
also be useful for exercising analysis workflows before running a real
experiment. API documentation is below.