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"]