nbs-bl 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nbs_bl/__init__.py +15 -0
- nbs_bl/beamline.py +450 -0
- nbs_bl/configuration.py +838 -0
- nbs_bl/detectors.py +89 -0
- nbs_bl/devices/__init__.py +12 -0
- nbs_bl/devices/detectors.py +154 -0
- nbs_bl/devices/motors.py +242 -0
- nbs_bl/devices/sampleholders.py +360 -0
- nbs_bl/devices/shutters.py +120 -0
- nbs_bl/devices/slits.py +51 -0
- nbs_bl/gGrEqns.py +171 -0
- nbs_bl/geometry/__init__.py +0 -0
- nbs_bl/geometry/affine.py +197 -0
- nbs_bl/geometry/bars.py +189 -0
- nbs_bl/geometry/frames.py +534 -0
- nbs_bl/geometry/linalg.py +138 -0
- nbs_bl/geometry/polygons.py +56 -0
- nbs_bl/help.py +126 -0
- nbs_bl/hw.py +270 -0
- nbs_bl/load.py +113 -0
- nbs_bl/motors.py +19 -0
- nbs_bl/planStatus.py +5 -0
- nbs_bl/plans/__init__.py +8 -0
- nbs_bl/plans/batches.py +174 -0
- nbs_bl/plans/conditions.py +77 -0
- nbs_bl/plans/flyscan_base.py +180 -0
- nbs_bl/plans/groups.py +55 -0
- nbs_bl/plans/maximizers.py +423 -0
- nbs_bl/plans/metaplans.py +179 -0
- nbs_bl/plans/plan_stubs.py +246 -0
- nbs_bl/plans/preprocessors.py +160 -0
- nbs_bl/plans/scan_base.py +58 -0
- nbs_bl/plans/scan_decorators.py +524 -0
- nbs_bl/plans/scans.py +145 -0
- nbs_bl/plans/suspenders.py +87 -0
- nbs_bl/plans/time_estimation.py +168 -0
- nbs_bl/plans/xas.py +123 -0
- nbs_bl/printing.py +221 -0
- nbs_bl/qt/models/beamline.py +11 -0
- nbs_bl/qt/models/energy.py +53 -0
- nbs_bl/qt/widgets/energy.py +225 -0
- nbs_bl/queueserver.py +249 -0
- nbs_bl/redisDevice.py +96 -0
- nbs_bl/run_engine.py +63 -0
- nbs_bl/samples.py +130 -0
- nbs_bl/settings.py +68 -0
- nbs_bl/shutters.py +39 -0
- nbs_bl/sim/__init__.py +2 -0
- nbs_bl/sim/config/polphase.nc +0 -0
- nbs_bl/sim/energy.py +403 -0
- nbs_bl/sim/manipulator.py +14 -0
- nbs_bl/sim/utils.py +36 -0
- nbs_bl/startup.py +27 -0
- nbs_bl/status.py +114 -0
- nbs_bl/tests/__init__.py +0 -0
- nbs_bl/tests/modify_regions.py +160 -0
- nbs_bl/tests/test_frames.py +99 -0
- nbs_bl/tests/test_panels.py +69 -0
- nbs_bl/utils.py +235 -0
- nbs_bl-0.2.0.dist-info/METADATA +71 -0
- nbs_bl-0.2.0.dist-info/RECORD +64 -0
- nbs_bl-0.2.0.dist-info/WHEEL +4 -0
- nbs_bl-0.2.0.dist-info/entry_points.txt +2 -0
- nbs_bl-0.2.0.dist-info/licenses/LICENSE +13 -0
nbs_bl/detectors.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from .help import add_to_func_list
|
|
2
|
+
from .beamline import GLOBAL_BEAMLINE
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_active_detectors():
|
|
6
|
+
return GLOBAL_BEAMLINE.detectors.active
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@add_to_func_list
|
|
10
|
+
def activate_detector(det_or_name, role=None):
|
|
11
|
+
"""Activate a detector so that is is measured by default
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
det_or_name : device or str
|
|
16
|
+
Either a device, or the name of a device in the global
|
|
17
|
+
detector buffer
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
# Todo: take a list
|
|
21
|
+
GLOBAL_BEAMLINE.detectors.activate(det_or_name, role)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@add_to_func_list
|
|
25
|
+
def list_detectors(verbose=False):
|
|
26
|
+
"""List all global detectors, optionally provide text descriptions
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
describe : Bool
|
|
31
|
+
If True, print the text description of each detector
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def textFunction(group, key, device):
|
|
36
|
+
text = f"{key}: {device.name}; {group.status[key]}"
|
|
37
|
+
return text
|
|
38
|
+
|
|
39
|
+
GLOBAL_BEAMLINE.detectors.describe(verbose, textFunction)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@add_to_func_list
|
|
43
|
+
def disable_detector(det_or_name):
|
|
44
|
+
"""Disable a detector so that it is not measured, and will not be activated
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
det_or_name : device or str
|
|
49
|
+
Either a device, or the name of a device in the global
|
|
50
|
+
detector buffer
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
GLOBAL_BEAMLINE.detectors.disable(det_or_name)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@add_to_func_list
|
|
57
|
+
def enable_detector(det_or_name, activate=True):
|
|
58
|
+
"""Enable a detector so that it can be activated
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
det_or_name : device or str
|
|
63
|
+
Either a device, or the name of a device in the global
|
|
64
|
+
detector buffer
|
|
65
|
+
activate : Bool
|
|
66
|
+
If True, also activate the detector when it is enabled
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
GLOBAL_BEAMLINE.detectors.enable(det_or_name, activate)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@add_to_func_list
|
|
73
|
+
def deactivate_detector(det_or_name):
|
|
74
|
+
"""Deactivate a detector so that it is not measured by default
|
|
75
|
+
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
det_or_name : device or str
|
|
79
|
+
Either a device, or the name of a device in the global
|
|
80
|
+
detector buffer
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
GLOBAL_BEAMLINE.detectors.deactivate(det_or_name)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@add_to_func_list
|
|
88
|
+
def activate_detector_set(name):
|
|
89
|
+
GLOBAL_BEAMLINE.detectors.activate_set(name)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .detectors import ophScalar
|
|
2
|
+
from .motors import (
|
|
3
|
+
FlyableMotor,
|
|
4
|
+
DeadbandEpicsMotor,
|
|
5
|
+
DeadbandPVPositioner,
|
|
6
|
+
DeadbandMixin,
|
|
7
|
+
PseudoSingle,
|
|
8
|
+
FlyerMixin,
|
|
9
|
+
)
|
|
10
|
+
from .shutters import EPS_Shutter, ShutterSet
|
|
11
|
+
from .slits import Slits
|
|
12
|
+
from .sampleholders import Manipulator1AxBase, Manipulator4AxBase
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from ophyd import Device, Component as Cpt, EpicsSignal, Signal
|
|
2
|
+
from ophyd.status import DeviceStatus
|
|
3
|
+
import threading
|
|
4
|
+
from queue import Queue, Empty
|
|
5
|
+
import time
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ScalarBase(Device):
|
|
10
|
+
exposure_time = Cpt(Signal, value=1, name="exposure_time", kind="config")
|
|
11
|
+
mean = Cpt(Signal, name="", value=0, kind="hinted")
|
|
12
|
+
median = Cpt(Signal, name="median", value=0, kind="omitted")
|
|
13
|
+
std = Cpt(Signal, name="std", value=0, kind="omitted")
|
|
14
|
+
npts = Cpt(Signal, name="points", value=0, kind="omitted")
|
|
15
|
+
sum = Cpt(Signal, name="sum", value=0, kind="omitted")
|
|
16
|
+
rescale = Cpt(Signal, value=1, name="rescale", kind="config")
|
|
17
|
+
offset = Cpt(Signal, value=0, name="offset", kind="config")
|
|
18
|
+
gain = Cpt(Signal, value=1, name="gain", kind="config")
|
|
19
|
+
|
|
20
|
+
def __init__(self, *args, rescale=1, **kwargs):
|
|
21
|
+
self._flying = False
|
|
22
|
+
self._measuring = False
|
|
23
|
+
self._reading = False
|
|
24
|
+
self._flyer_buffer = []
|
|
25
|
+
self._flyer_time_buffer = []
|
|
26
|
+
super().__init__(*args, **kwargs)
|
|
27
|
+
self.mean.name = self.name
|
|
28
|
+
self.rescale.set(rescale).wait(timeout=60)
|
|
29
|
+
if "gain" in kwargs:
|
|
30
|
+
self.gain.set(kwargs["gain"]).wait(timeout=60)
|
|
31
|
+
|
|
32
|
+
def kickoff(self):
|
|
33
|
+
self._flyer_buffer = []
|
|
34
|
+
self._flyer_time_buffer = []
|
|
35
|
+
self._flyer_timestamp_buffer = []
|
|
36
|
+
self._flyer_queue = Queue()
|
|
37
|
+
kickoff_st = DeviceStatus(device=self)
|
|
38
|
+
kickoff_st.set_finished()
|
|
39
|
+
self._flying = True
|
|
40
|
+
if not self._measuring:
|
|
41
|
+
self.target.subscribe(self._aggregate, run=False)
|
|
42
|
+
self._measuring = True
|
|
43
|
+
|
|
44
|
+
return kickoff_st
|
|
45
|
+
|
|
46
|
+
def stage(self):
|
|
47
|
+
self._secret_buffer = []
|
|
48
|
+
self._secret_time_buffer = []
|
|
49
|
+
self._buffer = []
|
|
50
|
+
self._time_buffer = []
|
|
51
|
+
self._reading = True
|
|
52
|
+
if not self._measuring:
|
|
53
|
+
self.target.subscribe(self._aggregate, run=False)
|
|
54
|
+
self._measuring = True
|
|
55
|
+
return super().stage()
|
|
56
|
+
|
|
57
|
+
def unstage(self):
|
|
58
|
+
if self._measuring:
|
|
59
|
+
self.target.clear_sub(self._aggregate)
|
|
60
|
+
self._measuring = False
|
|
61
|
+
self._reading = False
|
|
62
|
+
return super().unstage()
|
|
63
|
+
|
|
64
|
+
def set_exposure(self, exp_time):
|
|
65
|
+
self.exposure_time.set(exp_time).wait(timeout=60)
|
|
66
|
+
|
|
67
|
+
def _aggregate(self, value, **kwargs):
|
|
68
|
+
scale_value = value * self.rescale.get() - self.offset.get()
|
|
69
|
+
t = time.time()
|
|
70
|
+
if self._reading:
|
|
71
|
+
self._buffer.append(scale_value)
|
|
72
|
+
self._time_buffer.append(t)
|
|
73
|
+
if self._flying:
|
|
74
|
+
event = dict()
|
|
75
|
+
event["time"] = t
|
|
76
|
+
event["data"] = dict()
|
|
77
|
+
event["timestamps"] = dict()
|
|
78
|
+
event["data"][self.name] = scale_value
|
|
79
|
+
event["timestamps"][self.name] = kwargs.get("timestamp", t)
|
|
80
|
+
self._flyer_buffer.append(scale_value)
|
|
81
|
+
self._flyer_time_buffer.append(t)
|
|
82
|
+
self._flyer_timestamp_buffer.append(kwargs.get("timestamp", t))
|
|
83
|
+
self._flyer_queue.put(event)
|
|
84
|
+
|
|
85
|
+
def _acquire(self, status):
|
|
86
|
+
self._buffer = []
|
|
87
|
+
self._time_buffer = []
|
|
88
|
+
time.sleep(self.exposure_time.get())
|
|
89
|
+
if len(self._buffer) == 0:
|
|
90
|
+
ntry = 10
|
|
91
|
+
n = 0
|
|
92
|
+
while len(self._buffer) < 1:
|
|
93
|
+
time.sleep(0.1 * self.exposure_time.get())
|
|
94
|
+
n += 1
|
|
95
|
+
if n > ntry:
|
|
96
|
+
break
|
|
97
|
+
buf = np.array(self._buffer)
|
|
98
|
+
tbuf = np.array(self._time_buffer[: len(buf)])
|
|
99
|
+
if len(buf) == 0:
|
|
100
|
+
self.mean.put(np.nan)
|
|
101
|
+
self.median.put(np.nan)
|
|
102
|
+
self.std.put(np.nan)
|
|
103
|
+
self.npts.put(0)
|
|
104
|
+
self.sum.put(np.nan)
|
|
105
|
+
|
|
106
|
+
else:
|
|
107
|
+
self.mean.put(np.mean(buf))
|
|
108
|
+
self.median.put(np.median(buf))
|
|
109
|
+
self.std.put(np.std(buf))
|
|
110
|
+
self.npts.put(len(buf))
|
|
111
|
+
self.sum.put(np.sum(buf))
|
|
112
|
+
self._secret_buffer.append(buf)
|
|
113
|
+
self._secret_time_buffer.append(tbuf)
|
|
114
|
+
status.set_finished()
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
def trigger(self):
|
|
118
|
+
status = DeviceStatus(self)
|
|
119
|
+
threading.Thread(target=self._acquire, args=(status,), daemon=True).start()
|
|
120
|
+
return status
|
|
121
|
+
|
|
122
|
+
def collect(self):
|
|
123
|
+
events = []
|
|
124
|
+
while True:
|
|
125
|
+
try:
|
|
126
|
+
e = self._flyer_queue.get_nowait()
|
|
127
|
+
events.append(e)
|
|
128
|
+
except Empty:
|
|
129
|
+
break
|
|
130
|
+
yield from events
|
|
131
|
+
|
|
132
|
+
def complete(self):
|
|
133
|
+
self._flying = False
|
|
134
|
+
if self._measuring:
|
|
135
|
+
self.target.clear_sub(self._aggregate)
|
|
136
|
+
self._measuring = False
|
|
137
|
+
completion_status = DeviceStatus(self)
|
|
138
|
+
completion_status.set_finished()
|
|
139
|
+
return completion_status
|
|
140
|
+
|
|
141
|
+
def describe_collect(self):
|
|
142
|
+
dd = dict(
|
|
143
|
+
{self.name: {"source": self.target.pvname, "dtype": "number", "shape": []}}
|
|
144
|
+
)
|
|
145
|
+
return {self.name + "_monitor": dd}
|
|
146
|
+
|
|
147
|
+
def get_plot_hints(self):
|
|
148
|
+
return [self.mean.name]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class ophScalar(ScalarBase):
|
|
152
|
+
"""Generic Scalar. Give full path to target PV during object creation"""
|
|
153
|
+
|
|
154
|
+
target = Cpt(EpicsSignal, "", kind="omitted")
|
nbs_bl/devices/motors.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
from queue import Queue, Empty
|
|
2
|
+
import time
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import threading
|
|
5
|
+
|
|
6
|
+
from ophyd import EpicsMotor, Signal, PositionerBase, Device
|
|
7
|
+
from ophyd.pv_positioner import PVPositioner
|
|
8
|
+
from ophyd import Component as Cpt
|
|
9
|
+
from ophyd.status import wait as status_wait, DeviceStatus
|
|
10
|
+
from ophyd import PseudoSingle as _PS
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FlyerMixin:
|
|
14
|
+
|
|
15
|
+
def __init__(self, *args, **kwargs):
|
|
16
|
+
self._ready_to_fly = False
|
|
17
|
+
self._fly_move_st = None
|
|
18
|
+
self._default_time_resolution = 0.1
|
|
19
|
+
self._time_resolution = None
|
|
20
|
+
super().__init__(*args, **kwargs)
|
|
21
|
+
|
|
22
|
+
# Flyer motor methods
|
|
23
|
+
def preflight(self, start, stop, speed=None, time_resolution=None):
|
|
24
|
+
self._old_velocity = self.velocity.get()
|
|
25
|
+
self._flyer_stop = stop
|
|
26
|
+
st = self.move(start)
|
|
27
|
+
if speed is None:
|
|
28
|
+
speed = self._old_velocity
|
|
29
|
+
self.velocity.set(speed).wait()
|
|
30
|
+
if time_resolution is not None:
|
|
31
|
+
self._time_resolution = time_resolution
|
|
32
|
+
else:
|
|
33
|
+
self._time_resolution = self._default_time_resolution
|
|
34
|
+
self._last_readback_value = start
|
|
35
|
+
self._ready_to_fly = True
|
|
36
|
+
return st
|
|
37
|
+
|
|
38
|
+
def fly(self):
|
|
39
|
+
"""
|
|
40
|
+
Should be called after all detectors start flying, so that we don't lose data
|
|
41
|
+
"""
|
|
42
|
+
if not self._ready_to_fly:
|
|
43
|
+
self._fly_move_st = DeviceStatus(device=self)
|
|
44
|
+
self._fly_move_st.set_finished(success=False)
|
|
45
|
+
else:
|
|
46
|
+
self._fly_move_st = self.move(self._flyer_stop, wait=False)
|
|
47
|
+
self._flying = True
|
|
48
|
+
self._ready_to_fly = False
|
|
49
|
+
return self._fly_move_st
|
|
50
|
+
|
|
51
|
+
def land(self):
|
|
52
|
+
if self._fly_move_st.done:
|
|
53
|
+
self.velocity.set(self._old_velocity).wait()
|
|
54
|
+
self._flying = False
|
|
55
|
+
self._time_resolution = None
|
|
56
|
+
|
|
57
|
+
# Flyer detector methods for readback
|
|
58
|
+
def kickoff(self):
|
|
59
|
+
kickoff_st = DeviceStatus(device=self)
|
|
60
|
+
self._flyer_queue = Queue()
|
|
61
|
+
self._measuring = True
|
|
62
|
+
self._flyer_buffer = []
|
|
63
|
+
threading.Thread(target=self._aggregate, daemon=True).start()
|
|
64
|
+
# self.user_readback.subscribe(self._aggregate, run=False)
|
|
65
|
+
kickoff_st.set_finished()
|
|
66
|
+
return kickoff_st
|
|
67
|
+
|
|
68
|
+
def _aggregate(self):
|
|
69
|
+
name = self.user_readback.name
|
|
70
|
+
while self._measuring:
|
|
71
|
+
t = time.time()
|
|
72
|
+
rb = self.user_readback.read()
|
|
73
|
+
value = rb[name]["value"]
|
|
74
|
+
ts = rb[name]["timestamp"]
|
|
75
|
+
self._flyer_buffer.append(value)
|
|
76
|
+
event = dict()
|
|
77
|
+
event["time"] = t
|
|
78
|
+
event["data"] = dict()
|
|
79
|
+
event["timestamps"] = dict()
|
|
80
|
+
event["data"][name] = value
|
|
81
|
+
event["timestamps"][name] = ts
|
|
82
|
+
self._flyer_queue.put(event)
|
|
83
|
+
time.sleep(self._time_resolution)
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
def collect(self):
|
|
87
|
+
events = []
|
|
88
|
+
while True:
|
|
89
|
+
try:
|
|
90
|
+
e = self._flyer_queue.get_nowait()
|
|
91
|
+
events.append(e)
|
|
92
|
+
except Empty:
|
|
93
|
+
break
|
|
94
|
+
yield from events
|
|
95
|
+
|
|
96
|
+
def complete(self):
|
|
97
|
+
if self._measuring:
|
|
98
|
+
# self.user_readback.clear_sub(self._aggregate)
|
|
99
|
+
self._measuring = False
|
|
100
|
+
completion_status = DeviceStatus(self)
|
|
101
|
+
completion_status.set_finished()
|
|
102
|
+
self._time_resolution = None
|
|
103
|
+
return completion_status
|
|
104
|
+
|
|
105
|
+
def describe_collect(self):
|
|
106
|
+
if hasattr(self, "user_readback"):
|
|
107
|
+
readback = self.user_readback
|
|
108
|
+
else:
|
|
109
|
+
readback = self.readback
|
|
110
|
+
|
|
111
|
+
dd = dict(
|
|
112
|
+
{
|
|
113
|
+
readback.name: {
|
|
114
|
+
"source": readback.pvname,
|
|
115
|
+
"dtype": "number",
|
|
116
|
+
"shape": [],
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
return {self.name + "_monitor": dd}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class DeadbandMixin(Device, PositionerBase):
|
|
124
|
+
"""
|
|
125
|
+
Should be the leftmost class in the inheritance list so that it grabs move first!
|
|
126
|
+
|
|
127
|
+
Must be combined with either EpicsMotor or PVPositioner, or some other class
|
|
128
|
+
that has a done_value attribute
|
|
129
|
+
|
|
130
|
+
An EpicsMotor subclass that has an absolute tolerance for moves.
|
|
131
|
+
If the readback is within tolerance of the setpoint, the MoveStatus
|
|
132
|
+
is marked as finished, even if the motor is still settling.
|
|
133
|
+
|
|
134
|
+
This prevents motors with long, but irrelevant, settling times from
|
|
135
|
+
adding overhead to scans.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
tolerance = Cpt(Signal, value=-1, kind="config")
|
|
139
|
+
move_latch = Cpt(Signal, value=0, kind="omitted")
|
|
140
|
+
|
|
141
|
+
def __init__(self, *args, tolerance=None, **kwargs):
|
|
142
|
+
super().__init__(*args, **kwargs)
|
|
143
|
+
if tolerance is not None:
|
|
144
|
+
self.tolerance.put(tolerance)
|
|
145
|
+
|
|
146
|
+
def _done_moving(self, success=True, timestamp=None, value=None, **kwargs):
|
|
147
|
+
"""Call when motion has completed. Runs ``SUB_DONE`` subscription."""
|
|
148
|
+
if self.move_latch.get():
|
|
149
|
+
#print(f"{timestamp} {datetime.today().time().isoformat()} [{self.name}]: marked done")
|
|
150
|
+
if success:
|
|
151
|
+
self._run_subs(sub_type=self.SUB_DONE, timestamp=timestamp, value=value)
|
|
152
|
+
|
|
153
|
+
self._run_subs(
|
|
154
|
+
sub_type=self._SUB_REQ_DONE, success=success, timestamp=timestamp
|
|
155
|
+
)
|
|
156
|
+
self._reset_sub(self._SUB_REQ_DONE)
|
|
157
|
+
self.move_latch.put(0)
|
|
158
|
+
|
|
159
|
+
def move(self, position, wait=True, **kwargs):
|
|
160
|
+
tolerance = self.tolerance.get()
|
|
161
|
+
|
|
162
|
+
if tolerance < 0:
|
|
163
|
+
self.move_latch.put(1)
|
|
164
|
+
return super().move(position, wait=wait, **kwargs)
|
|
165
|
+
else:
|
|
166
|
+
status = super().move(position, wait=False, **kwargs)
|
|
167
|
+
setpoint = position
|
|
168
|
+
done_value = getattr(self, "done_value", 1)
|
|
169
|
+
|
|
170
|
+
def check_deadband(value, timestamp, **kwargs):
|
|
171
|
+
if abs(value - setpoint) < tolerance:
|
|
172
|
+
#print(f"{timestamp} {datetime.today().time().isoformat()} [{self.name}]: {value} within {tolerance} of {setpoint}")
|
|
173
|
+
self._done_moving(
|
|
174
|
+
timestamp=timestamp, success=True, value=done_value
|
|
175
|
+
)
|
|
176
|
+
else:
|
|
177
|
+
pass
|
|
178
|
+
# print(f"{timestamp}: {self.name}, {value} not within {tolerance} of {setpoint}")
|
|
179
|
+
|
|
180
|
+
def clear_deadband(*args, **kwargs):
|
|
181
|
+
# print(f"{timestamp}: Ran deadband clear for {self.name}")
|
|
182
|
+
self.clear_sub(check_deadband, event_type=self.SUB_READBACK)
|
|
183
|
+
|
|
184
|
+
self.subscribe(clear_deadband, event_type=self._SUB_REQ_DONE, run=False)
|
|
185
|
+
self.move_latch.put(1)
|
|
186
|
+
self.subscribe(check_deadband, event_type=self.SUB_READBACK, run=True)
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
if wait:
|
|
190
|
+
status_wait(status)
|
|
191
|
+
except KeyboardInterrupt:
|
|
192
|
+
self.stop()
|
|
193
|
+
raise
|
|
194
|
+
|
|
195
|
+
return status
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class DeadbandEpicsMotor(DeadbandMixin, EpicsMotor):
|
|
199
|
+
"""
|
|
200
|
+
An EpicsMotor subclass that has an absolute tolerance for moves.
|
|
201
|
+
If the readback is within tolerance of the setpoint, the MoveStatus
|
|
202
|
+
is marked as finished, even if the motor is still settling.
|
|
203
|
+
|
|
204
|
+
This prevents motors with long, but irrelevant, settling times from
|
|
205
|
+
adding overhead to scans.
|
|
206
|
+
|
|
207
|
+
This class is designed to be subclassed.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class DeadbandPVPositioner(DeadbandMixin, PVPositioner):
|
|
214
|
+
"""
|
|
215
|
+
A PVPositioner subclass that has an absolute tolerance for moves.
|
|
216
|
+
If the readback is within tolerance of the setpoint, the MoveStatus
|
|
217
|
+
is marked as finished, even if the motor is still settling.
|
|
218
|
+
|
|
219
|
+
This prevents motors with long, but irrelevant, settling times from
|
|
220
|
+
adding overhead to scans.
|
|
221
|
+
|
|
222
|
+
This class is designed to be subclassed.
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
pass
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class FlyableMotor(EpicsMotor, FlyerMixin):
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class PseudoSingle(_PS):
|
|
233
|
+
"""
|
|
234
|
+
A PseudoSingle subclass that casts positions to a float in order to ensure
|
|
235
|
+
that motor values don't get interpreted as int by Bluesky/Tiled.
|
|
236
|
+
|
|
237
|
+
If the first motor value is an int (i.e, 700 eV for the mono), Tiled will
|
|
238
|
+
try to cast all subsequent motor positions to an int as well.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
def move(self, position, *args, **kwargs):
|
|
242
|
+
return super().move(float(position), *args, **kwargs)
|