Basic, Part B: Motor and Move#
From APS Python Training for Bluesky Data Acquisition.
Objective
Work with an EPICS positioning motor (for precise positioning) in Bluesky and related tools.
First, we’ll connect with an EPICS motor (using ophyd), and then use the Bluesky software to scan the motor (with the scaler from basic_scaler).
Load ophyd
device support for the EpicsMotor
and connect with one EPICS motor channel. We have a synApps XXX-style IOC with the prefix gp:
. It has a scaler, 16 soft channel motors, and some other support we’ll ignore in this lesson.
note: This tutorial expects to find an EPICS IOC on the local network configured as a synApps xxx
IOC with prefix gp:
.
A docker container is available to provide this IOC. See this URL for instructions: prjemian/epics-docker
[1]:
from ophyd import EpicsMotor
m1 = EpicsMotor("gp:m1", name="m1")
m1.wait_for_connection()
Show the current value of the motor (that’s the .RBV
field, in case you were interested).
[2]:
print(f"{m1.position = }")
m1.position = 1.0
Connect the scaler (as was done in the basic_scaler lesson). Define some of the channel names and clear out others.
[3]:
from ophyd.scaler import ScalerCH
scaler = ScalerCH("gp:scaler1", name="scaler")
scaler.wait_for_connection()
# Since there are no detectors actually connected to this scaler,
# we can change names at our choice. A real scaler will have
# detectors connected to specific channels and we should not modify
# these names without regard to how signals are physically connected.
scaler.channels.chan01.chname.put("clock")
scaler.channels.chan02.chname.put("I0")
scaler.channels.chan03.chname.put("scint")
scaler.channels.chan04.chname.put("")
scaler.channels.chan05.chname.put("")
scaler.channels.chan06.chname.put("")
scaler.channels.chan07.chname.put("")
scaler.channels.chan08.chname.put("")
scaler.channels.chan09.chname.put("")
Use only the channels with EPICS names, those are the interesting channels.
[4]:
scaler.select_channels(None)
scaler.read()
[4]:
OrderedDict([('clock', {'value': 16000000.0, 'timestamp': 1684257260.223683}),
('I0', {'value': 7.0, 'timestamp': 1684257260.223683}),
('scint', {'value': 8.0, 'timestamp': 1684257260.223683}),
('scaler_time', {'value': 1.6, 'timestamp': 1684257260.223683})])
Create a RunEngine but do not connect it with a data collection strategy. That will come in the next lessons.
[5]:
from bluesky import RunEngine
import bluesky.plans as bp
RE = RunEngine({})
Run a step scan using the motor and the scaler.
[6]:
RE(bp.scan([scaler], m1, -1, 1, 5))
[6]:
('588b3330-2fc0-48db-8943-0383db1d22f0',)
Ah, yes. Nothing to see here since we did not setup anything to receive the documents from the RunEngine. Here’s the basic callback from the basic_scaler lesson.
[7]:
import pprint
def myCallback(key, doc):
print()
print(key, len(doc))
pprint.pprint(doc)
Repeat the same scan but handle the document stream with myCallback()
.
[8]:
RE(bp.scan([scaler], m1, -1, 1, 5), myCallback)
start 15
{'detectors': ['scaler'],
'hints': {'dimensions': [(['m1'], 'primary')]},
'motors': ('m1',),
'num_intervals': 4,
'num_points': 5,
'plan_args': {'args': ["EpicsMotor(prefix='gp:m1', name='m1', "
'settle_time=0.0, timeout=None, '
"read_attrs=['user_readback', 'user_setpoint'], "
"configuration_attrs=['user_offset', "
"'user_offset_dir', 'velocity', 'acceleration', "
"'motor_egu'])",
-1,
1],
'detectors': ["ScalerCH(prefix='gp:scaler1', name='scaler', "
"read_attrs=['channels', 'channels.chan01', "
"'channels.chan01.s', 'channels.chan02', "
"'channels.chan02.s', 'channels.chan03', "
"'channels.chan03.s', 'time'], "
"configuration_attrs=['channels', "
"'channels.chan01', 'channels.chan01.chname', "
"'channels.chan01.preset', "
"'channels.chan01.gate', 'channels.chan02', "
"'channels.chan02.chname', "
"'channels.chan02.preset', "
"'channels.chan02.gate', 'channels.chan03', "
"'channels.chan03.chname', "
"'channels.chan03.preset', "
"'channels.chan03.gate', 'count_mode', 'delay', "
"'auto_count_delay', 'freq', 'preset_time', "
"'auto_count_time', 'egu'])"],
'num': 5,
'per_step': 'None'},
'plan_name': 'scan',
'plan_pattern': 'inner_product',
'plan_pattern_args': {'args': ["EpicsMotor(prefix='gp:m1', name='m1', "
'settle_time=0.0, timeout=None, '
"read_attrs=['user_readback', "
"'user_setpoint'], "
"configuration_attrs=['user_offset', "
"'user_offset_dir', 'velocity', "
"'acceleration', 'motor_egu'])",
-1,
1],
'num': 5},
'plan_pattern_module': 'bluesky.plan_patterns',
'plan_type': 'generator',
'scan_id': 2,
'time': 1684257283.2970834,
'uid': '27c27453-9632-4f8c-b0a7-602c5db0a2e1',
'versions': {'bluesky': '1.10.0', 'ophyd': '1.7.0'}}
descriptor 8
{'configuration': {'m1': {'data': {'m1_acceleration': 0.2,
'm1_motor_egu': 'degrees',
'm1_user_offset': 0.0,
'm1_user_offset_dir': 0,
'm1_velocity': 1.0},
'data_keys': OrderedDict([('m1_user_offset',
{'dtype': 'number',
'lower_ctrl_limit': -1e+300,
'precision': 4,
'shape': [],
'source': 'PV:gp:m1.OFF',
'units': 'degrees',
'upper_ctrl_limit': 1e+300}),
('m1_user_offset_dir',
{'dtype': 'integer',
'enum_strs': ('Pos',
'Neg'),
'lower_ctrl_limit': None,
'shape': [],
'source': 'PV:gp:m1.DIR',
'units': None,
'upper_ctrl_limit': None}),
('m1_velocity',
{'dtype': 'number',
'lower_ctrl_limit': 0.1,
'precision': 4,
'shape': [],
'source': 'PV:gp:m1.VELO',
'units': 'degrees',
'upper_ctrl_limit': 0.0}),
('m1_acceleration',
{'dtype': 'number',
'lower_ctrl_limit': -1e+300,
'precision': 4,
'shape': [],
'source': 'PV:gp:m1.ACCL',
'units': 'sec',
'upper_ctrl_limit': 1e+300}),
('m1_motor_egu',
{'dtype': 'string',
'lower_ctrl_limit': None,
'shape': [],
'source': 'PV:gp:m1.EGU',
'units': None,
'upper_ctrl_limit': None})]),
'timestamps': {'m1_acceleration': 1684257260.388981,
'm1_motor_egu': 1684257260.388981,
'm1_user_offset': 1684257260.388981,
'm1_user_offset_dir': 1684257260.388981,
'm1_velocity': 1684257260.388981}},
'scaler': {'data': {'scaler_auto_count_delay': 0.0,
'scaler_auto_count_time': 1.0,
'scaler_channels_chan01_chname': 'clock',
'scaler_channels_chan01_gate': 'Y',
'scaler_channels_chan01_preset': 15000000.0,
'scaler_channels_chan02_chname': 'I0',
'scaler_channels_chan02_gate': 'N',
'scaler_channels_chan02_preset': 0.0,
'scaler_channels_chan03_chname': 'scint',
'scaler_channels_chan03_gate': 'N',
'scaler_channels_chan03_preset': 0.0,
'scaler_count_mode': 'OneShot',
'scaler_delay': 0.0,
'scaler_egu': '',
'scaler_freq': 10000000.0,
'scaler_preset_time': 1.5},
'data_keys': OrderedDict([('scaler_channels_chan01_chname',
{'dtype': 'string',
'lower_ctrl_limit': None,
'shape': [],
'source': 'PV:gp:scaler1.NM1',
'units': None,
'upper_ctrl_limit': None}),
('scaler_channels_chan01_preset',
{'dtype': 'number',
'lower_ctrl_limit': 0.0,
'precision': 0,
'shape': [],
'source': 'PV:gp:scaler1.PR1',
'units': '',
'upper_ctrl_limit': 0.0}),
('scaler_channels_chan01_gate',
{'dtype': 'string',
'enum_strs': ('N',
'Y'),
'lower_ctrl_limit': None,
'shape': [],
'source': 'PV:gp:scaler1.G1',
'units': None,
'upper_ctrl_limit': None}),
('scaler_channels_chan02_chname',
{'dtype': 'string',
'lower_ctrl_limit': None,
'shape': [],
'source': 'PV:gp:scaler1.NM2',
'units': None,
'upper_ctrl_limit': None}),
('scaler_channels_chan02_preset',
{'dtype': 'number',
'lower_ctrl_limit': 0.0,
'precision': 0,
'shape': [],
'source': 'PV:gp:scaler1.PR2',
'units': '',
'upper_ctrl_limit': 0.0}),
('scaler_channels_chan02_gate',
{'dtype': 'string',
'enum_strs': ('N',
'Y'),
'lower_ctrl_limit': None,
'shape': [],
'source': 'PV:gp:scaler1.G2',
'units': None,
'upper_ctrl_limit': None}),
('scaler_channels_chan03_chname',
{'dtype': 'string',
'lower_ctrl_limit': None,
'shape': [],
'source': 'PV:gp:scaler1.NM3',
'units': None,
'upper_ctrl_limit': None}),
('scaler_channels_chan03_preset',
{'dtype': 'number',
'lower_ctrl_limit': 0.0,
'precision': 0,
'shape': [],
'source': 'PV:gp:scaler1.PR3',
'units': '',
'upper_ctrl_limit': 0.0}),
('scaler_channels_chan03_gate',
{'dtype': 'string',
'enum_strs': ('N',
'Y'),
'lower_ctrl_limit': None,
'shape': [],
'source': 'PV:gp:scaler1.G3',
'units': None,
'upper_ctrl_limit': None}),
('scaler_count_mode',
{'dtype': 'string',
'enum_strs': ('OneShot',
'AutoCount'),
'lower_ctrl_limit': None,
'shape': [],
'source': 'PV:gp:scaler1.CONT',
'units': None,
'upper_ctrl_limit': None}),
('scaler_delay',
{'dtype': 'number',
'lower_ctrl_limit': 0.0,
'precision': 3,
'shape': [],
'source': 'PV:gp:scaler1.DLY',
'units': '',
'upper_ctrl_limit': 0.0}),
('scaler_auto_count_delay',
{'dtype': 'number',
'lower_ctrl_limit': 0.0,
'precision': 3,
'shape': [],
'source': 'PV:gp:scaler1.DLY1',
'units': '',
'upper_ctrl_limit': 0.0}),
('scaler_freq',
{'dtype': 'number',
'lower_ctrl_limit': 0.0,
'precision': 3,
'shape': [],
'source': 'PV:gp:scaler1.FREQ',
'units': '',
'upper_ctrl_limit': 0.0}),
('scaler_preset_time',
{'dtype': 'number',
'lower_ctrl_limit': 0.0,
'precision': 3,
'shape': [],
'source': 'PV:gp:scaler1.TP',
'units': '',
'upper_ctrl_limit': 0.0}),
('scaler_auto_count_time',
{'dtype': 'number',
'lower_ctrl_limit': 0.0,
'precision': 3,
'shape': [],
'source': 'PV:gp:scaler1.TP1',
'units': '',
'upper_ctrl_limit': 0.0}),
('scaler_egu',
{'dtype': 'string',
'lower_ctrl_limit': None,
'shape': [],
'source': 'PV:gp:scaler1.EGU',
'units': None,
'upper_ctrl_limit': None})]),
'timestamps': {'scaler_auto_count_delay': 1684257287.283159,
'scaler_auto_count_time': 1684257287.283159,
'scaler_channels_chan01_chname': 1684257287.283159,
'scaler_channels_chan01_gate': 1684257287.283159,
'scaler_channels_chan01_preset': 1684257287.283159,
'scaler_channels_chan02_chname': 1684257287.283159,
'scaler_channels_chan02_gate': 1684257287.283159,
'scaler_channels_chan02_preset': 1684257287.283159,
'scaler_channels_chan03_chname': 1684257287.283159,
'scaler_channels_chan03_gate': 1684257287.283159,
'scaler_channels_chan03_preset': 1684257287.283159,
'scaler_count_mode': 1684257287.283159,
'scaler_delay': 1684257287.283159,
'scaler_egu': 1684257287.283159,
'scaler_freq': 1684257287.283159,
'scaler_preset_time': 1684257287.283159}}},
'data_keys': {'I0': {'dtype': 'number',
'lower_ctrl_limit': 0.0,
'object_name': 'scaler',
'precision': 0,
'shape': [],
'source': 'PV:gp:scaler1.S2',
'units': '',
'upper_ctrl_limit': 0.0},
'clock': {'dtype': 'number',
'lower_ctrl_limit': 0.0,
'object_name': 'scaler',
'precision': 0,
'shape': [],
'source': 'PV:gp:scaler1.S1',
'units': '',
'upper_ctrl_limit': 0.0},
'm1': {'dtype': 'number',
'lower_ctrl_limit': -1000.0,
'object_name': 'm1',
'precision': 4,
'shape': [],
'source': 'PV:gp:m1.RBV',
'units': 'degrees',
'upper_ctrl_limit': 1000.0},
'm1_user_setpoint': {'dtype': 'number',
'lower_ctrl_limit': -1000.0,
'object_name': 'm1',
'precision': 4,
'shape': [],
'source': 'PV:gp:m1.VAL',
'units': 'degrees',
'upper_ctrl_limit': 1000.0},
'scaler_time': {'dtype': 'number',
'lower_ctrl_limit': 0.0,
'object_name': 'scaler',
'precision': 3,
'shape': [],
'source': 'PV:gp:scaler1.T',
'units': '',
'upper_ctrl_limit': 0.0},
'scint': {'dtype': 'number',
'lower_ctrl_limit': 0.0,
'object_name': 'scaler',
'precision': 0,
'shape': [],
'source': 'PV:gp:scaler1.S3',
'units': '',
'upper_ctrl_limit': 0.0}},
'hints': {'m1': {'fields': ['m1']},
'scaler': {'fields': ['clock', 'I0', 'scint']}},
'name': 'primary',
'object_keys': {'m1': ['m1', 'm1_user_setpoint'],
'scaler': ['clock', 'I0', 'scint', 'scaler_time']},
'run_start': '27c27453-9632-4f8c-b0a7-602c5db0a2e1',
'time': 1684257287.3060765,
'uid': 'facadfef-8068-4c5b-b641-c9ebb91e1220'}
event 7
{'data': {'I0': 8.0,
'clock': 16000000.0,
'm1': -1.0,
'm1_user_setpoint': -1.0,
'scaler_time': 1.6,
'scint': 8.0},
'descriptor': 'facadfef-8068-4c5b-b641-c9ebb91e1220',
'filled': {},
'seq_num': 1,
'time': 1684257288.5562477,
'timestamps': {'I0': 1684257287.283159,
'clock': 1684257287.283159,
'm1': 1684257285.657212,
'm1_user_setpoint': 1684257283.400607,
'scaler_time': 1684257287.283159,
'scint': 1684257287.283159},
'uid': 'e3cadd5d-36aa-4d65-a86c-e5e3b40f38db'}
event 7
{'data': {'I0': 9.0,
'clock': 16000000.0,
'm1': -0.5,
'm1_user_setpoint': -0.5,
'scaler_time': 1.6,
'scint': 7.0},
'descriptor': 'facadfef-8068-4c5b-b641-c9ebb91e1220',
'filled': {},
'seq_num': 2,
'time': 1684257290.9914892,
'timestamps': {'I0': 1684257290.988253,
'clock': 1684257290.988253,
'm1': 1684257289.36312,
'm1_user_setpoint': 1684257288.603839,
'scaler_time': 1684257290.988253,
'scint': 1684257290.988253},
'uid': '3b448615-cfbf-4fa6-949b-bfc5f38669db'}
event 7
{'data': {'I0': 7.0,
'clock': 16000000.0,
'm1': 0.0,
'm1_user_setpoint': 0.0,
'scaler_time': 1.6,
'scint': 5.0},
'descriptor': 'facadfef-8068-4c5b-b641-c9ebb91e1220',
'filled': {},
'seq_num': 3,
'time': 1684257293.3976073,
'timestamps': {'I0': 1684257293.391206,
'clock': 1684257293.391206,
'm1': 1684257291.768174,
'm1_user_setpoint': 1684257291.056937,
'scaler_time': 1684257293.391206,
'scint': 1684257293.391206},
'uid': 'd170727a-038b-432a-9fa4-a14d9857e41d'}
event 7
{'data': {'I0': 7.0,
'clock': 16000000.0,
'm1': 0.5,
'm1_user_setpoint': 0.5,
'scaler_time': 1.6,
'scint': 8.0},
'descriptor': 'facadfef-8068-4c5b-b641-c9ebb91e1220',
'filled': {},
'seq_num': 4,
'time': 1684257295.9012296,
'timestamps': {'I0': 1684257295.898317,
'clock': 1684257295.898317,
'm1': 1684257294.272727,
'm1_user_setpoint': 1684257293.50185,
'scaler_time': 1684257295.898317,
'scint': 1684257295.898317},
'uid': '47180649-a9b0-4a10-b380-95541a096c24'}
event 7
{'data': {'I0': 9.0,
'clock': 16000000.0,
'm1': 1.0,
'm1_user_setpoint': 1.0,
'scaler_time': 1.6,
'scint': 7.0},
'descriptor': 'facadfef-8068-4c5b-b641-c9ebb91e1220',
'filled': {},
'seq_num': 5,
'time': 1684257298.4070554,
'timestamps': {'I0': 1684257298.403889,
'clock': 1684257298.403889,
'm1': 1684257296.778319,
'm1_user_setpoint': 1684257295.981387,
'scaler_time': 1684257298.403889,
'scint': 1684257298.403889},
'uid': '303a8aa8-8fdb-44c6-8366-1b1cc3000b08'}
stop 6
{'exit_status': 'success',
'num_events': {'primary': 5},
'reason': '',
'run_start': '27c27453-9632-4f8c-b0a7-602c5db0a2e1',
'time': 1684257298.5027833,
'uid': 'ed996294-1aaf-4052-b82b-c1b7d5bc9a3a'}
[8]:
('27c27453-9632-4f8c-b0a7-602c5db0a2e1',)
Summary#
We’ll show this code as a python program:
#!/usr/bin/env python
"""Basic : motor"""
from ophyd import EpicsMotor
from ophyd.scaler import ScalerCH
from bluesky import RunEngine
import bluesky.plans as bp
def myCallback(key, doc):
print()
print(key, len(doc))
pprint.pprint(doc)
m1 = EpicsMotor("gp:m1", name="m1")
m1.wait_for_connection()
print(m1.position)
scaler = ScalerCH("gp:scaler1", name="scaler")
scaler.wait_for_connection()
# Since there are no detectors actually connected to this scaler,
# we can change names at our choice. A real scaler will have
# detectors connected to specific channels and we should not modify
# these names without regard to how signals are physically connected.
scaler.channels.chan01.chname.put("clock")
scaler.channels.chan02.chname.put("I0")
scaler.channels.chan03.chname.put("scint")
scaler.channels.chan04.chname.put("")
scaler.channels.chan05.chname.put("")
scaler.channels.chan06.chname.put("")
scaler.channels.chan07.chname.put("")
scaler.channels.chan08.chname.put("")
scaler.channels.chan09.chname.put("")
scaler.match_names()
scaler.select_channels()
print(scaler.read())
RE = RunEngine({})
RE(bp.scan([scaler], m1, -1, 1, 5))
RE(bp.scan([scaler], m1, -1, 1, 5), myCallback)