Source code for id3c.plans.ad_plugins
"""Support for area detector file plugins (HDF5, TIFF, ...).
.. autosummary::
~read_ad_plugin_components
~set_ad_plugin_components
The set of components visible to either helper is determined by each
component's ``kind`` (``hinted``, ``normal``, or ``config``). To
expose or hide a component, adjust its ``kind`` attribute (for
example ``eiger2.hdf1.num_capture.kind = "config"``).
The set helper is intended for plugin configuration *before*
acquisition -- an alternative to ophyd's staging process. Actionable
signals (``capture``, ``acquire``, ...) are normally driven by
staging and should not be set through this helper. Order of the
``bps.mv`` calls inside the set helper is undefined; if ordering
matters, make separate calls.
"""
from bluesky import plan_stubs as bps
from bluesky.utils import plan
from ophyd import Kind
from ophyd import Signal
from pyRestTable import Table
[docs]
REPORTABLE_KINDS = Kind.hinted | Kind.normal | Kind.config
_NO_VALUE = object() # sentinel distinct from any value the IOC might return
def _select_ad_plugin_keys(plugin):
"""Yield component names that are Signals with a reportable kind."""
# ``Kind.omitted`` is ``0`` and ``IntFlag.__contains__`` treats ``0`` as
# a subset of any mask, so the kind check uses bitwise intersection
# instead of ``in``.
for key in sorted(plugin.component_names):
obj = getattr(plugin, key)
if isinstance(obj, Signal) and bool(obj.kind & REPORTABLE_KINDS):
yield key
[docs]
def read_ad_plugin_components(plugin):
"""Print a table of plugin components (access, name, current value).
Diagnostic only. Use this interactively to discover which keys
can be passed to :func:`set_ad_plugin_components`. Components
whose ``.read()`` returns no ``value`` field are skipped.
"""
table = Table()
table.labels = ["Access", "Component", "Value"]
for key in _select_ad_plugin_keys(plugin):
obj = getattr(plugin, key)
access = "R" if obj.read_access else "-"
access += "W" if obj.write_access else "-"
reading = obj.read()
first = next(iter(reading.values()), {})
value = first.get("value", _NO_VALUE)
if value is _NO_VALUE:
continue
table.addRow([access, key, value])
if len(table.rows):
print(table)
@plan
[docs]
def set_ad_plugin_components(plugin, /, **kwargs):
"""Set plugin components by keyword.
Each keyword names a component on ``plugin``; the value is the
new setpoint. All writes are issued through a single
``bps.mv(...)`` and complete in parallel; order is undefined.
Raises:
TypeError: any kwarg names a component that is not writable.
KeyError: any kwarg names a component the helper does not
see (unknown name, or its ``kind`` is
``omitted``).
"""
known = set(_select_ad_plugin_keys(plugin))
unknown = sorted(set(kwargs) - known)
ro_keys = sorted(
key for key in kwargs if key in known and not getattr(plugin, key).write_access
)
if ro_keys:
raise TypeError(f"Cannot set read-only components: {', '.join(ro_keys)}")
if unknown:
raise KeyError(f"Unknown plugin components: {', '.join(unknown)}")
movables = []
for key in kwargs:
movables += [getattr(plugin, key), kwargs[key]]
if movables:
yield from bps.mv(*movables)
else:
# No kwargs: still emit one message so the @plan wrapper does
# not raise RuntimeWarning at garbage-collection time.
yield from bps.null()