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.
See also
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.
- class bluesky.protocols.Status(*args, **kwargs)[source]#
If success
is False
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.” A FailedStatus
exception will be raised
inside the RunEngine.
Additionally, Status
objects may (optionally) add a watch function that
conforms to the following definition
- 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.The
fraction
argument accepts a single float representing fraction remaining. A fraction of zero indicates completion. A fraction of one indicates progress has not started.
Named Device#
Some of the interfaces below require a name
attribute, they implement this
interface:
- class bluesky.protocols.HasName(*args, **kwargs)[source]#
Bases:
Protocol
- abstract property name: str#
Used to populate object_keys in the Event DataKey
https://blueskyproject.io/event-model/event-descriptors.html#object-keys
Some of also require a parent
attribute, they implement this interface:
Readable Device#
To produce data in a step scan, a device must be Readable:
- class bluesky.protocols.Readable(*args, **kwargs)[source]#
-
- abstract describe() dict[str, DataKey] | Awaitable[dict[str, DataKey]] [source]#
Return an OrderedDict with exactly the same keys as the
read
method, here mapped to per-scan metadata about each field.This can be a standard function or an
async
function.Example return value:
OrderedDict(('channel1', {'source': 'XF23-ID:SOME_PV_NAME', 'dtype': 'number', 'shape': []}), ('channel2', {'source': 'XF23-ID:SOME_PV_NAME', 'dtype': 'number', 'shape': []}))
- abstract read() dict[str, Reading[T]] | Awaitable[dict[str, Reading[T]]] [source]#
Return an OrderedDict mapping string field name(s) to dictionaries of values and timestamps and optional per-point metadata.
This can be a standard function or an
async
function.Example return value:
OrderedDict(('channel1', {'value': 5, 'timestamp': 1472493713.271991}), ('channel2', {'value': 16, 'timestamp': 1472493713.539238}))
A dict of stream name to Descriptors is returned from describe()
, where a
Descriptor is a dictionary with the following keys:
- class bluesky.protocols.DataKey[source]#
Describes the objects in the data property of Event documents
A dict of stream name to Reading is returned from read()
, where a
Reading is a dictionary with the following keys:
- class bluesky.protocols.Reading[source]#
A dictionary containing the value and timestamp of a piece of scan data
- value: T#
The current value, as a JSON encodable type or numpy array
The following keys can optionally be present in a Reading:
- class bluesky.protocols.ReadingOptional[source]#
A dictionary containing the optional per-reading metadata of a piece of scan data
If the device has configuration that only needs to be read once at the start of scan, the following interface can be implemented:
- class bluesky.protocols.Configurable(*args, **kwargs)[source]#
Bases:
Protocol
[T
]
If a device needs to do something before it can be read, the following interface can be implemented:
External Asset Writing Interface#
Devices that write their data in external files, rather than returning directly
from read()
should implement the following interface:
- class bluesky.protocols.WritesExternalAssets(*args, **kwargs)[source]#
Bases:
Protocol
- abstract collect_asset_docs() Iterator[tuple[Literal['resource'], PartialResource] | tuple[Literal['datum'], Datum]] | AsyncIterator[tuple[Literal['resource'], PartialResource] | tuple[Literal['datum'], Datum]] [source]#
- Create the resource and datum documents describing data in external
source.
Example yielded values:
('resource', { 'path_semantics': 'posix', 'resource_kwargs': {'frame_per_point': 1}, 'resource_path': 'det.h5', 'root': '/tmp/tmpcvxbqctr/', 'spec': 'AD_HDF5', 'uid': '9123df61-a09f-49ae-9d23-41d4d6c6d788' }) # or ('datum', { 'datum_id': '9123df61-a09f-49ae-9d23-41d4d6c6d788/0', 'datum_kwargs': {'point_number': 0}, 'resource': '9123df61-a09f-49ae-9d23-41d4d6c6d788'} })
The yielded values are a tuple of the document type and the document as a dictionary.
A Resource will be yielded to show that data will be written to an external resource like a file on disk:
While a Datum will be yielded to specify a single frame of data in a Resource:
Movable (or “Settable”) Device#
The interface of a movable device extends the interface of a readable device with the following additional methods and attributes.
- class bluesky.protocols.Movable(*args, **kwargs)[source]#
Bases:
Protocol
[T_co
]- position#
A optional heuristic that describes the current position of a device as a single scalar, as opposed to the potentially multi-valued description provided by
read()
.Note
The position attribute has been deprecated in favour of the Locatable protocol below
Certain plans like mvr()
would like to know where a
Device was last requested to move to, and other plans like
rd()
would like to know where a Device is currently
located. Devices may implement locate()
to provide this information.
- class bluesky.protocols.Locatable(*args, **kwargs)[source]#
-
- abstract locate() Location[T] | Awaitable[Location[T]] [source]#
Return the current location of a Device.
While a
Readable
reports many values, aMovable
will have the concept of location. This is where the Device currently is, and where it was last requested to move to. This protocol formalizes how to get the location from aMovable
.
Location
objects are dictionaries with the following entries:
“Flyer” Interface#
For context on what we mean by “flyer”, refer to the section on Asynchronous Acquisition.
The interface of a “flyable” device is separate from the interface of a readable or settable device, though there is some overlap.
- class bluesky.protocols.Flyable(*args, **kwargs)[source]#
The yielded values from collect()
are partial Event dictionaries:
If any of the data keys are in external assets rather than including the data,
a filled
key should be present:
Flyable devices can also implement Configurable
if they have
configuration that only needs to be read once at the start of scan
Optional Interfaces#
These are additional interfaces for providing optional behavior to Readable
, Movable
,
and Flyable
devices.
The methods described here are either hooks for various plans/RunEngine messages which are ignored if not present or required by only a subset of RunEngine messages. In the latter case, the RunEngine may error if it tries to use a device which does not define the required method.
- class bluesky.protocols.Stageable(*args, **kwargs)[source]#
Bases:
Protocol
- class bluesky.protocols.Subscribable(*args, **kwargs)[source]#
- class bluesky.protocols.Pausable(*args, **kwargs)[source]#
Bases:
Protocol
- class bluesky.protocols.Stoppable(*args, **kwargs)[source]#
Bases:
Protocol
- abstract stop(success=True) None | Awaitable[None] [source]#
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.This can be a standard function or an
async
function.
- class bluesky.protocols.Checkable(*args, **kwargs)[source]#
Bases:
Protocol
[T_co
]- abstract check_value(value: T_co) None | Awaitable[None] [source]#
Test for a valid setpoint without actually moving.
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.This method is used by simulators that check limits. If not implemented those simulators should assume all values are valid, but may warn.
This method may be used during a scan, so should not write to any Signals
This can be a standard function or an
async
function.
- class bluesky.protocols.Hints[source]#
A dictionary of optional hints for visualization
- class bluesky.protocols.Preparable(*args, **kwargs)[source]#
Bases:
Protocol
- abstract prepare(value) Status [source]#
Prepare a device for scanning.
This method provides similar functionality to
Stageable.stage
andMovable.set
, with key differences:Stageable.stage
#Staging a device translates to, “I’m going to use this in a scan, but I’m not sure how”. Preparing it translates to, “I’m about to do a step or a fly scan with these parameters”. Staging should be universal across many different types of scans, however prepare is specific to an input value passed in.
Movable.set
#For some devices, preparation for a scan could involve multiple soft or hardware signals being configured and/or set.
prepare
therefore allows these to be bundled together, along with other logic.For example, a Flyable device should have the following methods called on it to perform a fly-scan:
prepare(flyscan_params) kickoff() complete()
If the device is a detector,
collect_asset_docs
can be called repeatedly whilecomplete
is not done to publish frames. Alternatively, to step-scan a detector,prepare(frame_params) to setup N software triggered frames trigger() to take N frames collect_asset_docs() to publish N frames
Returns a Status that is marked done when the device is ready for a scan.
Checking if an object supports an interface#
You can check at runtime if an object supports an interface with isinstance
:
from bluesky.protocols import Readable
assert isinstance(obj, Readable)
obj.read()
This will check that the correct methods exist on the object, and are callable, but will not check any types.
There is also a helper function for this:
- bluesky.protocols.check_supports(obj: T, protocol: type[Any]) T [source]#
Check that an object supports a protocol
This exists so that multiple protocol checks can be run in a mypy compatible way, e.g.:
triggerable = check_supports(obj, Triggerable) triggerable.trigger() readable = check_supports(obj, Readable) readable.read()