instrument.devices#

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

factories

Device factories

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:

1creator(*, 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 creator, "gp:m1" is the prefix, and the other kwargs are name and labels.

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

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

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

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 ‘creator’ (Python code) and creates any devices listed below it. In YAML:

  • Each ‘creator’ can only be listed once.

  • All devices that are created with a ‘creator’ 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: ["motors"]}
 9
10ophyd.EpicsMotor: [{name: m1, prefix: ioc:m1, labels: ["motors"]}]
11
12instrument.devices.factories.motors:
13- {prefix: ioc:m, names: m, first: 1, last: 1, labels: ["motors"]}

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: ["motors"]}
3- {name: m2, prefix: ioc:m2, labels: ["motors"]}
4- {name: m3, prefix: ioc:m3, labels: ["motors"]}
5- {name: dx, prefix: vme:m58:c0:m1, labels: ["motors"]}
6- {name: dy, prefix: vme:m58:c0:m2, labels: ["motors"]}

Using a factory to define some of these motors that fit a numerical pattern:

1instrument.devices.factories.motors:
2- {prefix: ioc:m, names: m, first: 1, last: 3, labels: ["motors"]}
3
4ophyd.EpicsMotor:
5- {name: dx, prefix: vme:m58:c0:m1, labels: ["motors"]}
6- {name: dy, prefix: vme:m58:c0:m2, labels: ["motors"]}

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=["motors"])
19    omega = FCpt(EpicsMotor, "{prefix}{m_omega}", kind="hinted", labels=["motors"])
20    chi = FCpt(EpicsMotor, "{prefix}{m_chi}", kind="hinted", labels=["motors"])
21    phi = FCpt(EpicsMotor, "{prefix}{m_phi}", kind="hinted", labels=["motors"])
22    gamma = FCpt(EpicsMotor, "{prefix}{m_gamma}", kind="hinted", labels=["motors"])
23    delta = FCpt(EpicsMotor, "{prefix}{m_delta}", kind="hinted", labels=["motors"])
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"]

Device factories#

Device factories are used to:

  • Create several similar ophyd-style devices (such as ophyd.Device or ophyd.Signal) that fit a pattern.

  • Import a device which is pre-defined in a module, such as the ophyd simulators in ophyd.sim.

factory_base(*[, prefix, names, first, ...])

Make one or more objects using 'creator'.

motors(*[, prefix, names, first, last])

Make one or more 'ophyd.EpicsMotor' objects.

predefined_device(*[, name, creator])

Provide a predefined device such as from the 'ophyd.sim' module.

instrument.devices.factories.factory_base(*, prefix=None, names='object{}', first=0, last=0, creator='ophyd.Signal', **kwargs)[source]#

Make one or more objects using ‘creator’.

PARAMETERS

prefixstr

Prefix pattern for the EPICS PVs (default: None).

namesstr

Name pattern for the objects. The default pattern is "object{}" which produces devices named object1, object2, ..., `. If a formatting specification ({}) is not provided, it is appended. Each object will be named using this code: names.format(number), such as:

In [23]: "object{}".format(22)
Out[23]: 'object22'
firstint

The first object number in the continuous series from ‘first’ through ‘last’ (inclusive).

lastint

The first object number in the continuous series from ‘first’ through ‘last’ (inclusive).

creatorstr

Name of the creator code that will be used to construct each device. (default: "ophyd.Signal")

kwargsdict

Dictionary of additional keyword arguments. This is included when creating each object.

instrument.devices.factories.motors(*, prefix=None, names='m{}', first=0, last=0, **kwargs)[source]#

Make one or more ‘ophyd.EpicsMotor’ objects.

Example entry in devices.yml file:

1instrument.devices.factories.motors:
2  - {prefix: "ioc:m", first: 1, last: 4, labels: ["motor"]}
3  # skip m5 & m6
4  - {prefix: "ioc:m", first: 7, last: 22, labels: ["motor"]}

Uses this pattern:

1ophyd.EpicsMotor(
2        prefix=prefix.format(i),
3        name=names.format(i),
4        **kwargs,
5    )

where i iterates from ‘first’ through ‘last’ (inclusive).

PARAMETERS

prefixstr

Name pattern for the EPICS PVs. There is no default pattern. If a formatting specification ({}) is not provided, it is appended (as with other ophyd devices). Each motor will be configured with this prefix: prefix.format(number), such as:

In [23]: "ioc:m{}".format(22)
Out[23]: 'ioc:m22'
namesstr

Name pattern for the motors. The default pattern is "m{}" which produces motors named m1, m2, ..., m22, m23, ...`. If a formatting specification ({}) is not provided, it is appended. Each motor will be named using this code: names.format(number), such as:

In [23]: "m{}".format(22)
Out[23]: 'm22'
firstint

The first motor number in the continuous series from ‘first’ through ‘last’ (inclusive).

lastint

The first motor number in the continuous series from ‘first’ through ‘last’ (inclusive).

kwargsdict

Dictionary of additional keyword arguments. This is included with each EpicsMotor object.

instrument.devices.factories.predefined_device(*, name='', creator='')[source]#

Provide a predefined device such as from the ‘ophyd.sim’ module.

PARAMETERS

creatorstr

Name of the predefined device to be used

namestr

Simulator will be assigned this name. (default: use existing name)

Example entry in devices.yml file:

1instrument.devices.factories.predefined_device:
2  - {creator: ophyd.sim.motor, name: sim_motor}
3  - {creator: ophyd.sim.noisy_det, name: sim_det}