Devices Guide#
Device Loading at Runtime#
Devices are managed through a set of helper functions available after startup:
# List all available devices (from devices.yml)
find_loadable_devices()
# Filter by label
find_loadable_devices(label="4idg")
find_loadable_devices(name="transfocator") # substring match
find_loadable_devices(name="kb", exact_name=False)
# Connect a device
load_device("transfocator")
# Disconnect and remove from baseline
remove_device("transfocator")
# Reload everything from YAML (useful after devices.yml edits)
reload_all_devices()
reload_all_devices(stations=["core", "4idh"])
These are thin wrappers around
id4_common.utils.device_loader — see the
API reference for full
parameter documentation.
Deferred EPICS Connection Pattern#
make_devices() instantiates every device object from devices.yml without
making EPICS connections. This means startup is fast and does not depend on the
EPICS network being reachable.
Only connect_device() — called internally by load_device() — triggers actual
EPICS interaction via wait_for_connection().
_post_connect_setup() hook#
If your device needs to subscribe to PV values or perform any post-connection work, implement this method:
from ophyd import Device, Component, EpicsSignalRO
class MyDevice(Device):
signal = Component(EpicsSignalRO, "PV:NAME")
def _post_connect_setup(self):
"""Called by connect_device() after EPICS connection is live."""
self.signal.subscribe(self._on_change, run=False)
def _on_change(self, value, **kwargs):
...
connect_device() calls _post_connect_setup() automatically after
wait_for_connection() succeeds. For sub-components (not top-level
devices.yml entries), always use run=False on any subscribe() calls
inside __init__ to avoid fetching PV values before the connection is live.
Factory Pattern for Dynamic Components#
Where a DynamicDeviceComponent must be assembled at class-definition time
(e.g. a variable number of channels), use a factory function that returns a
new class:
def make_transfocator_class(ioc="4idgSoft:"):
class Transfocator(Device):
lenses = DynamicDeviceComponent(_build_lens_dict(ioc))
...
return Transfocator
# Module-level default (what devices.yml points to)
Transfocator = make_transfocator_class()
Real examples in the codebase:
Factory |
Module |
|---|---|
|
|
|
|
Notable Device Classes#
Area Detectors#
Class |
Module |
Notes |
|---|---|---|
|
|
Dectris Eiger 1M |
|
|
X-Spectrum Lambda |
|
|
Princeton LightField spectrometer |
|
|
Allied Vision Vimba camera |
Fluorescence Detectors#
Class |
Module |
|---|---|
|
|
|
|
|
|
Optics and Beam Delivery#
Class |
Module |
Notes |
|---|---|---|
|
|
KB mirror pair (factory-built) |
|
|
Compound refractive lens (factory-built) |
|
|
Si(111) DCM |
|
|
Tracks mono energy; other devices can subscribe |
|
|
Upstream/downstream undulators |
|
|
X-ray beam position monitor |
Diffractometer#
Class |
Module |
Notes |
|---|---|---|
|
|
Huber Euler 6-circle (hklpy2) |
Experiment Utilities#
Class |
Module |
Notes |
|---|---|---|
|
|
Magnet power supply control |
|
|
Time-resolved chopper |
|
|
Fast shutter |
|
|
Multi-channel scaler (SIS3820) |
Counters Class#
CountersClass (from id4_common.utils.counters_class) holds the active
detector and monitor selection used by all scan plans:
# Default counters object is available as `counters` after startup
counters.detectors # list of active detectors
counters.monitor # scaler channel used as monitor
counters.extra_devices # devices read but not plotted
# Change the selection
counters.plotselect(det_idx=0, mon_idx=1)
Scan plans automatically read counters.detectors and counters.monitor
unless overridden by explicit arguments.