instrument.devices

See the advice below to declare your instrument’s ophyd-style in YAML files:

The instrument.startup module calls RE(make_devices()) to make the devices as described.

Declare all devices

All ophyd-style devices are declared in one of the two YAML files listed above:

  • when code (classes, factory functions) is provided by a package: * refer to the package class (or function) directly in the YAML file

  • when local customization is needed (new support or modify a packaged class): * create local custom code that defines the class or factory * refer to local custom code in the YAML file

Any device can be created with python code that is compatible with this API signature:

1callable(*, prefix="", name="", labels=[], **kwargs)

Quick example

An ophyd object for an EPICS motor PV gp:m1 is created in Python code where ophyd.EpicsMotor is the callable, "gp:m1" is the prefix, and the other kwargs are name and labels.

1import ophyd
2m1 = ophyd.EpicsMotor("ioc:m1", name="m1", labels=["motor"])

This YAML replaces all the Python code above to create the m1 object:

1ophyd.EpicsMotor:
2- name: "m1"
3  prefix: "ioc:m1"
4  labels: ["motor"]

Tip

The devices are (re)created each time RE(make_devices()) is run.

If you edit either of these YAML files after starting your session, you can run RE(make_devices()) again (without restarting your session) to (re)create the devices. You only need to restart your session if you edit the Python code.

The make_devices() plan stub imports the callable and creates any devices listed below it. In YAML:

  • Each callable can only be listed once.

  • All devices that are created with a callable are listed below it.

  • Each device starts with a - and then the kwargs, as shown.

Indentation is important. Follow the examples.

Tip

These YAML representations are functionally equivalent:

See yaml.org for more information and YAML examples.

 1ophyd.EpicsMotor:
 2- name: m1
 3  prefix: ioc:m1
 4  labels:
 5    - motor
 6
 7ophyd.EpicsMotor:
 8- {name: m1, prefix: ioc:m1, labels: ["motor"]}
 9
10ophyd.EpicsMotor: [{name: m1, prefix: ioc:m1, labels: ["motor"]}]

Examples

Examples are provided to show how to define your ophyd-style devices.

Packaged support

Packaged support exists for many structures (motors, scalers, area detectors, slits, shutters, …).

Motors

When support is used for more than one device, create a YAML list. Each item in the list can be a dictionary with appropriate keyword arguments. This YAML code describes five motors, using a one-line format for each dictionary.

1ophyd.EpicsMotor:
2- {name: m1, prefix: ioc:m1, labels: ["motor"]}
3- {name: m2, prefix: ioc:m2, labels: ["motor"]}
4- {name: m3, prefix: ioc:m3, labels: ["motor"]}
5- {name: dx, prefix: vme:m58:c0:m1, labels: ["motor"]}
6- {name: dy, prefix: vme:m58:c0:m2, labels: ["motor"]}

Scalers

This example creates a single scaler named scaler. Two keyword arguments are provided.

1ophyd.scaler.ScalerCH:
2- name: scaler
3  prefix: ioc:scaler1
4  labels: ["scalers", "detectors"]

Area detectors

An area detector factory (from the apstools package) can be used to declare one or more area detector devices. Here’s an instance of ADSimDetector with various plugins.

 1apstools.devices.ad_creator:
 2- name: adsimdet
 3  prefix: "ad:"
 4  labels: ["area_detector", "detectors"]
 5  plugins:
 6  - cam:
 7        class: apstools.devices.SimDetectorCam_V34
 8  - image
 9  - pva
10  - hdf1:
11        class: apstools.devices.AD_EpicsFileNameHDF5Plugin
12        read_path_template: "/mnt/iocad/tmp/"
13        write_path_template: "/tmp/"
14  - roi1
15  - stats1

Local custom devices

Sometimes, a package provides support that requires some local customization.

diffractometers

While the hklpy package provides a 6-circle diffractometer, it does not provide a class with name substitutions for the motor axes. We need those substitutions to describe our diffractometer’s motor assignments. (That’s a DIY feature for improvement in a future version of hklpy.) We’ll have to make some local code that provides motor name substitutions as keyword arguments.

Here’s the local support code (in new file src/instrument/devices/diffractometers.py):

 1"""Diffractometers"""
 2
 3import hkl
 4from ophyd import Component
 5from ophyd import EpicsMotor
 6from ophyd import EpicsSignalRO
 7from ophyd import FormattedComponent as FCpt
 8
 9class SixCircle(hkl.SimMixin, hkl.E6C):
10    """
11    Our 6-circle.  Eulerian.
12
13    Energy obtained (RO) from monochromator.
14    """
15
16    # the reciprocal axes are defined by SimMixin
17
18    mu = FCpt(EpicsMotor, "{prefix}{m_mu}", kind="hinted", labels=["motor"])
19    omega = FCpt(EpicsMotor, "{prefix}{m_omega}", kind="hinted", labels=["motor"])
20    chi = FCpt(EpicsMotor, "{prefix}{m_chi}", kind="hinted", labels=["motor"])
21    phi = FCpt(EpicsMotor, "{prefix}{m_phi}", kind="hinted", labels=["motor"])
22    gamma = FCpt(EpicsMotor, "{prefix}{m_gamma}", kind="hinted", labels=["motor"])
23    delta = FCpt(EpicsMotor, "{prefix}{m_delta}", kind="hinted", labels=["motor"])
24
25    energy = Component(EpicsSignalRO, "BraggERdbkAO", kind="hinted", labels=["energy"])
26    energy_units = Component(EpicsSignalRO, "BraggERdbkAO.EGU", kind="config")
27
28    def __init__(  # noqa D107
29        self,
30        prefix,
31        *,
32        m_mu="",
33        m_omega="",
34        m_chi="",
35        m_phi="",
36        m_gamma="",
37        m_delta="",
38        **kwargs,
39    ):
40        self.m_mu = m_mu
41        self.m_omega = m_omega
42        self.m_chi = m_chi
43        self.m_phi = m_phi
44        self.m_gamma = m_gamma
45        self.m_delta = m_delta
46        super().__init__(prefix, **kwargs)

The YAML description of our 6-circle diffractometer uses our local custom SixCircle support with the assigned motors and other kwargs:

 1instrument.devices.diffractometers.SixCircle:
 2- name: sixc
 3  prefix: "gp:"
 4  labels: ["diffractometer"]
 5  m_mu: m23
 6  m_omega: m24
 7  m_chi: m25
 8  m_phi: m26
 9  m_gamma: m27
10  m_delta: m28

Using the devices

The instrument.utils.make_devices_yaml.make_devices() plan stub adds all devices to the command line level (the __main__ namespace, as Python calls it). Plans or other code can obtain a reference to any of these devices through use of the oregistry. The default instrument provides a shutter device. This setup_shutter plan stub configures the shutter to wait a finite time every time it opens or closes.

 1def setup_shutter(delay=0.05):
 2    """
 3    Setup the shutter.
 4
 5    Simulate a shutter that needs a finite recovery time after moving.
 6    """
 7    yield from bps.null()  # makes it a plan (generator function)
 8
 9    shutter = oregistry["shutter"]
10    shutter.wait_for_connection()
11    shutter.delay_s = delay

With this YAML content:

1apstools.synApps.UserCalcsDevice: [{name: user_calcs, prefix: "gp:"}]

you might have a plan stub that needs two of the userCalcs. The oregistry can provide them to your plan stub:

1dither_x = oregistry["user_calcs.calc9"]
2dither_y = oregistry["user_calcs.calc10"]