dls-dodal 1.56.0__py3-none-any.whl → 1.57.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dls-dodal
3
- Version: 1.56.0
3
+ Version: 1.57.0
4
4
  Summary: Ophyd devices and other utils that could be used across DLS beamlines
5
5
  Author-email: Dominic Oram <dominic.oram@diamond.ac.uk>, Joseph Ware <joseph.ware@diamond.ac.uk>, Oliver Silvester <Oliver.Silvester@diamond.ac.uk>, Noemi Frisina <noemi.frisina@diamond.ac.uk>
6
6
  License: Apache License
@@ -215,7 +215,7 @@ Description-Content-Type: text/markdown
215
215
  License-File: LICENSE
216
216
  Requires-Dist: click
217
217
  Requires-Dist: ophyd
218
- Requires-Dist: ophyd-async[ca,pva]>=0.13.0
218
+ Requires-Dist: ophyd-async[ca,pva]>=0.13.2
219
219
  Requires-Dist: bluesky==1.14.2
220
220
  Requires-Dist: pyepics
221
221
  Requires-Dist: dataclasses-json
@@ -1,7 +1,7 @@
1
- dls_dodal-1.56.0.dist-info/licenses/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
1
+ dls_dodal-1.57.0.dist-info/licenses/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
2
2
  dodal/__init__.py,sha256=Ksms_WJF8LTkbm38gEpm1jBpGqcQ8NGvmb2ZJlOE1j8,198
3
3
  dodal/__main__.py,sha256=kP2S2RPitnOWpNGokjZ1Yq-1umOtp5sNOZk2B3tBPLM,111
4
- dodal/_version.py,sha256=-Q-x6fbTDcv8A0qE6FFdxYCjVSPznhUJwbWxT9so9xg,706
4
+ dodal/_version.py,sha256=UMWpFq_XEVs_RQua2c3vn_WoxKKPZqViS9Btwn1PIkY,706
5
5
  dodal/cli.py,sha256=yi8dXOp0hqzlg4ZZXCRGU-LpDa_ydaropDjyREWbZ5Y,4152
6
6
  dodal/log.py,sha256=Rt5O3hFZfMnJvQueZvgagQuXnPqHrFxhponOvVkpfrk,9871
7
7
  dodal/utils.py,sha256=abGitd4FLpLnmckF7lUqOKYUL88r5Ex_NGSVgO4gOf4,19305
@@ -29,14 +29,15 @@ dodal/beamlines/i09_2.py,sha256=lyYO1rOaIsXNuVOL39Psh-8jQjkhQBXEQMpbXa-Zcw0,713
29
29
  dodal/beamlines/i10.py,sha256=0rSWOlIihkDtq7ZZaJ5O2jkMBrteRYjl4Hhqe8EP1Yc,5697
30
30
  dodal/beamlines/i11.py,sha256=rjb5iARfbToNzeP-gewYljTu0f__V7bYj3JkJ5bWkGE,4349
31
31
  dodal/beamlines/i13_1.py,sha256=VYVqMN8-njy7YSI08gskRccT-K2paRC9edAx0ah-Cwo,1602
32
+ dodal/beamlines/i17.py,sha256=Nickt8CKQ9JcQ1D_ulNICUT4jjLF1Aib7D9jblSnzA4,987
32
33
  dodal/beamlines/i18.py,sha256=FuU8G-q1piu6BRou-Shj3BQEbNtsF7CUsSIqqkvCKZc,3615
33
34
  dodal/beamlines/i19_1.py,sha256=tg3eALJTn9p686VFV7GjfUvChGS1mrh8-98uzjdLj_g,2934
34
35
  dodal/beamlines/i19_2.py,sha256=xSMS2Vhk_6V5WYyefVo1SWXbC4UUyaqru7JVK8VTg00,2554
35
36
  dodal/beamlines/i19_optics.py,sha256=8hdlDAAMgFrhcXrp5xCPZtLUlrDUEC9VwKnnuUAMbbU,1150
36
37
  dodal/beamlines/i20_1.py,sha256=Zsr1lsH7ySbOgK7RhMVMWzNWZAV-fuYW0iAjSEJZicY,2625
37
- dodal/beamlines/i22.py,sha256=VU5gz0q_K1D4zBHb88xnd1nAISAIBZOF6o8_YrGPGck,8103
38
+ dodal/beamlines/i22.py,sha256=009Tk5cYyHfmm1DfIwR562WzomXRvyv_5chiBbPgQUs,8100
38
39
  dodal/beamlines/i23.py,sha256=ZXvPEiMA4mPbRTXOxvL1NcoVWDg4Deyl8k57cveDg90,3060
39
- dodal/beamlines/i24.py,sha256=f6SeNbMdxviurfTAnKps8S9NNbDpU7SFzr-J1_c0yn8,6833
40
+ dodal/beamlines/i24.py,sha256=FvK7MqC_dX_alUFMEoS8L4HACThky9lQ7w7rwy0gXdA,6259
40
41
  dodal/beamlines/k11.py,sha256=sBOl-MLk7FMo10KFykuK5aLqH1zyW4FnI0XXmtQhUfg,978
41
42
  dodal/beamlines/p38.py,sha256=lxFGzmQhth0JhOFbTrZd9LEbUgvKmQdMtiHDW6UJgDs,5763
42
43
  dodal/beamlines/p45.py,sha256=tQ7EkWfr7o2okK7Prw1C7DNGYdKPU2ofqjm98wu_-G0,2158
@@ -90,15 +91,15 @@ dodal/devices/robot.py,sha256=k4Vkjpd3R-wXWf6YbYC1225sSdjvTrZS54v5v6Qy_EU,7003
90
91
  dodal/devices/s4_slit_gaps.py,sha256=4KdarIQoRqX4ry3LUS1Km7fkjUFahA0VuTd2DvYEqQ8,446
91
92
  dodal/devices/scintillator.py,sha256=JresF8SY_-t1raibzR4f0UoMXnFi-Abh3ywGm2DjhKs,3003
92
93
  dodal/devices/slits.py,sha256=b_7ku2sHlzhMHTvWrwiRwee6ufrbxNX9JB_Z0lvk15o,1105
93
- dodal/devices/smargon.py,sha256=TC1ye35mDT1gLlrjIe3CcGeE2vEb78Cu7f0DbzUQR8M,5813
94
+ dodal/devices/smargon.py,sha256=48oOBOKlsvPhlUlK41ozXzCMUKMp3QkQx-pVqKL2Xts,6569
94
95
  dodal/devices/status.py,sha256=hVrJS1yooQo6PRumRACoIEh-SKBUKxvBlQl-MtLFUMQ,327
95
96
  dodal/devices/synchrotron.py,sha256=OHBrTrm4K39XE8BrE9b_Jn_ZfMRyDp9CHCwvmiV-KOc,1989
96
97
  dodal/devices/tetramm.py,sha256=qBu2ClXRM4RFO-y4C2pXVKsp5sX4VpOelbVHOAkkUHQ,8936
97
- dodal/devices/thawer.py,sha256=UtikbirSZZGjcbZEDWO33n5CIsd_dSvmohIS4R65xWg,1571
98
+ dodal/devices/thawer.py,sha256=HexahKZ1rUxF9jBMeULruqYt6I7fsl6OgXkvcxPQY3M,1963
98
99
  dodal/devices/turbo_slit.py,sha256=xhcnhfbdcTYSYozogw6Li4fF4ofoPsc350rEyrRdaNE,1460
99
100
  dodal/devices/undulator.py,sha256=3IRn1iOF1PLOuIzaMpsNLVYx-rGvAnr-PG7C3YupW-w,5266
100
101
  dodal/devices/watsonmarlow323_pump.py,sha256=xNwjoxW3NJIDkeDWHfb0A8Yj95_KKRXMD9AghvX-WLk,1337
101
- dodal/devices/webcam.py,sha256=QUfOwblZr_xUGVqt5cMhTwfyQAL-jhSJ2ORQRNmZBrM,2436
102
+ dodal/devices/webcam.py,sha256=UAx2KF0mKi6I-mJJUb5z56MHY-Wd89-tqyPcFbouQFg,2491
102
103
  dodal/devices/xbpm_feedback.py,sha256=Uv_jML9gkMT5OcpUrckzxDmsBmktEwV1lga6USxeRyY,1065
103
104
  dodal/devices/aithre_lasershaping/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
104
105
  dodal/devices/aithre_lasershaping/goniometer.py,sha256=-2ewYMcdzB1DqP9sMpr4L1i4KeTCrGAJfngAt9-eFWw,1005
@@ -192,12 +193,11 @@ dodal/devices/i22/fswitch.py,sha256=kpgegs4Wv_weBSzbrlXLXqXOOZdzTn3X9k5PlEN5F6c,
192
193
  dodal/devices/i22/nxsas.py,sha256=093GCM2H2LESt28RF-JfxEljJ8QzRPkL1ByXI2XVhoA,6012
193
194
  dodal/devices/i24/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
194
195
  dodal/devices/i24/aperture.py,sha256=dlH_g7OPTBc5QRMd-ADU3_GDTKh668kkMHo4k_JxUcY,770
195
- dodal/devices/i24/beam_center.py,sha256=m6LWsG9e_lhtPfZ8pc_hoLNyTYQQGGdRNav8J_2scTo,483
196
+ dodal/devices/i24/beam_center.py,sha256=nQyOif6JtlU_qP7kZP-8wN1ldW81MIbFUykPC1bEm70,446
196
197
  dodal/devices/i24/beamstop.py,sha256=6tbiQLlcTlp4PCPhHJ_mlHtkv0kz5ArQ99zg9rwTnrw,1133
197
198
  dodal/devices/i24/dcm.py,sha256=l7qbJh2JKL-5ANlMYXDeU5EBYY6mDiLxn7kp_Z9KNaM,1206
198
199
  dodal/devices/i24/dual_backlight.py,sha256=N0R7M1mHPRmQ4fks5lGU1wrXjOvcW_ZKIXaRoC8aLDE,2084
199
200
  dodal/devices/i24/focus_mirrors.py,sha256=DYiYLpDw8FJ1LYHxLOxE_om5qGfUo2itzskgqhmQZlg,1763
200
- dodal/devices/i24/pilatus_metadata.py,sha256=I-AR8vd67qf6p0vZnRPTv4aNPN5T4KpSt2Iog4_jvn0,1781
201
201
  dodal/devices/i24/pmac.py,sha256=-HYf2HPzaqWvszp4T8TXohdp40-xmKqQq4V0mLvVri8,7028
202
202
  dodal/devices/i24/vgonio.py,sha256=sxSmcYZayVJPJz_D_91j9PmNor7Tbl1RGQFRrdtESlw,533
203
203
  dodal/devices/mx_phase1/beamstop.py,sha256=GeHLqHVf3XwJSszUHuxZ2JBwQlcDiFs6jVpdHuw7snM,3127
@@ -224,7 +224,6 @@ dodal/devices/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
224
224
  dodal/devices/util/adjuster_plans.py,sha256=c40PFZpXFw0YmJLh9jU4VIb8vRxHyafZlmvprTKAOhM,824
225
225
  dodal/devices/util/epics_util.py,sha256=4useFL8ngsVF08fhOn48BlnO4oh0T4sEKqjdS6mjvG0,4687
226
226
  dodal/devices/util/lookup_tables.py,sha256=jH9f_D8JbTSqzL-RKHUWOORLt8lEoNQL3o9HpXE98TY,3476
227
- dodal/devices/util/test_utils.py,sha256=tnKKmSBaHEyx-qTQjoR3COqraRMmTdRJe9s-pWjuTPk,1176
228
227
  dodal/devices/xspress3/xspress3.py,sha256=OerapEy-IuK7EFz13B5z0BzBmESVl6pYUlqAWHIwJck,4555
229
228
  dodal/devices/xspress3/xspress3_channel.py,sha256=w8tAx2lz5kJ_LeJ_eb_4o--Dtt8MRijsYNgDG6oEIVg,1626
230
229
  dodal/devices/zebra/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -250,11 +249,12 @@ dodal/plans/verify_undulator_gap.py,sha256=OcDN09-eCoMzsmhKGxvzsH5EapG2zYz0yGCqU
250
249
  dodal/plans/wrapped.py,sha256=BPMw__RcWvk9v5XnhMsi9_k4KsDEbmXogzD2n1ecbUg,2098
251
250
  dodal/plans/preprocessors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
252
251
  dodal/plans/preprocessors/verify_undulator_gap.py,sha256=cBZEGq8TW1jrXFXB00iClQVXSEaE_jP_rHMY9WTgYyY,1813
253
- dodal/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
252
+ dodal/testing/__init__.py,sha256=AUYZKAvVOs7ZvxO1dVhL0pDTleRO34FQlO5MNe_cwgU,96
253
+ dodal/testing/setup.py,sha256=8cQnrzE5MQD4Etf0eqMarmtr-opsUOMQww-k1V7DzIQ,2442
254
254
  dodal/testing/electron_analyser/__init__.py,sha256=-lc1opD2dCv0x678-J-ApOhHtvEvcslfOQ7E613U8-Y,118
255
255
  dodal/testing/electron_analyser/device_factory.py,sha256=tkMY6fW3iI02DTD1XXHi4lH6sjo8RHHZBGDHSuTdmNU,2243
256
- dls_dodal-1.56.0.dist-info/METADATA,sha256=NqXV5pD4dRmGxYRSjy-i--y0scKO6E0V14rJRehyguw,16928
257
- dls_dodal-1.56.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
258
- dls_dodal-1.56.0.dist-info/entry_points.txt,sha256=bycw_EKUzup_rxfCetOwcauXV4kLln_OPpPT8jEnr-I,94
259
- dls_dodal-1.56.0.dist-info/top_level.txt,sha256=xIozdmZk_wmMV4wugpq9-6eZs0vgADNUKz3j2UAwlhc,6
260
- dls_dodal-1.56.0.dist-info/RECORD,,
256
+ dls_dodal-1.57.0.dist-info/METADATA,sha256=8uZwtRMSABZLLYumDM2RDWn4zaE5IJ6N5H5zMk5Vz1w,16928
257
+ dls_dodal-1.57.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
258
+ dls_dodal-1.57.0.dist-info/entry_points.txt,sha256=bycw_EKUzup_rxfCetOwcauXV4kLln_OPpPT8jEnr-I,94
259
+ dls_dodal-1.57.0.dist-info/top_level.txt,sha256=xIozdmZk_wmMV4wugpq9-6eZs0vgADNUKz3j2UAwlhc,6
260
+ dls_dodal-1.57.0.dist-info/RECORD,,
dodal/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '1.56.0'
32
- __version_tuple__ = version_tuple = (1, 56, 0)
31
+ __version__ = version = '1.57.0'
32
+ __version_tuple__ = version_tuple = (1, 57, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
dodal/beamlines/i17.py ADDED
@@ -0,0 +1,37 @@
1
+ """The I17 hardware doesn't exist yet, but this configuration file is useful for
2
+ creating plans in sm-bluesky as devices build up."""
3
+
4
+ from ophyd_async.core import StrictEnum
5
+
6
+ from dodal.common.beamlines.beamline_utils import (
7
+ device_factory,
8
+ )
9
+ from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
10
+ from dodal.devices.pgm import PGM
11
+ from dodal.devices.synchrotron import Synchrotron
12
+ from dodal.log import set_beamline as set_log_beamline
13
+ from dodal.utils import BeamlinePrefix, get_beamline_name
14
+
15
+ BL = get_beamline_name("i17")
16
+ PREFIX = BeamlinePrefix(BL)
17
+ set_log_beamline(BL)
18
+ set_utils_beamline(BL)
19
+
20
+
21
+ class I17Grating(StrictEnum):
22
+ AU_400 = "400 line/mm Au"
23
+ SI_400 = "400 line/mm Si"
24
+
25
+
26
+ @device_factory()
27
+ def synchrotron() -> Synchrotron:
28
+ return Synchrotron()
29
+
30
+
31
+ @device_factory(skip=True)
32
+ def pgm() -> PGM:
33
+ return PGM(
34
+ prefix=f"{PREFIX.beamline_prefix}-OP-PGM-01:",
35
+ grating=I17Grating,
36
+ gratingPv="NLINES2",
37
+ )
dodal/beamlines/i22.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from pathlib import Path
2
2
 
3
3
  from ophyd_async.epics.adaravis import AravisDetector
4
- from ophyd_async.epics.adcore import NDPluginStatsIO
4
+ from ophyd_async.epics.adcore import NDPluginBaseIO
5
5
  from ophyd_async.epics.adpilatus import PilatusDetector
6
6
  from ophyd_async.fastcs.panda import HDFPanda
7
7
 
@@ -103,7 +103,7 @@ def i0() -> TetrammDetector:
103
103
  path_provider=get_path_provider(),
104
104
  type="Cividec Diamond XBPM",
105
105
  plugins={
106
- "stats": NDPluginStatsIO(
106
+ "stats": NDPluginBaseIO(
107
107
  prefix=f"{PREFIX.beamline_prefix}-EA-XBPM-02:SumAll:"
108
108
  )
109
109
  },
@@ -117,7 +117,7 @@ def it() -> TetrammDetector:
117
117
  path_provider=get_path_provider(),
118
118
  type="PIN Diode",
119
119
  plugins={
120
- "stats": NDPluginStatsIO(
120
+ "stats": NDPluginBaseIO(
121
121
  prefix=f"{PREFIX.beamline_prefix}-EA-TTRM-02:SumAll:"
122
122
  )
123
123
  },
dodal/beamlines/i24.py CHANGED
@@ -11,7 +11,6 @@ from dodal.devices.i24.beamstop import Beamstop
11
11
  from dodal.devices.i24.dcm import DCM
12
12
  from dodal.devices.i24.dual_backlight import DualBacklight
13
13
  from dodal.devices.i24.focus_mirrors import FocusMirrorsMode
14
- from dodal.devices.i24.pilatus_metadata import PilatusMetadata
15
14
  from dodal.devices.i24.pmac import PMAC
16
15
  from dodal.devices.i24.vgonio import VerticalGoniometer
17
16
  from dodal.devices.motors import YZStage
@@ -100,8 +99,8 @@ def dcm() -> DCM:
100
99
  If this is called when already instantiated in i24, it will return the existing object.
101
100
  """
102
101
  return DCM(
103
- prefix=f"{PREFIX.beamline_prefix}-DI-DCM-01",
104
- motion_prefix=f"{PREFIX.beamline_prefix}-MO-DCM-01",
102
+ prefix=f"{PREFIX.beamline_prefix}-DI-DCM-01:",
103
+ motion_prefix=f"{PREFIX.beamline_prefix}-MO-DCM-01:",
105
104
  )
106
105
 
107
106
 
@@ -188,21 +187,3 @@ def eiger_beam_center() -> DetectorBeamCenter:
188
187
  f"{PREFIX.beamline_prefix}-EA-EIGER-01:CAM:",
189
188
  "eiger_bc",
190
189
  )
191
-
192
-
193
- @device_factory()
194
- def pilatus_beam_center() -> DetectorBeamCenter:
195
- """A device for setting/reading the beamcenter from the pilatus on i24."""
196
- return DetectorBeamCenter(
197
- f"{PREFIX.beamline_prefix}-EA-PILAT-01:cam1:",
198
- "pilatus_bc",
199
- )
200
-
201
-
202
- @device_factory()
203
- def pilatus_metadata() -> PilatusMetadata:
204
- """A small pilatus driver device for figuring out the filename template."""
205
- return PilatusMetadata(
206
- f"{PREFIX.beamline_prefix}-EA-PILAT-01:",
207
- "pilatus_meta",
208
- )
@@ -1,5 +1,4 @@
1
- """A small temporary device to get the beam center positions from \
2
- eiger or pilatus detector on i24"""
1
+ """A small temporary device to get the beam center positions on i24"""
3
2
 
4
3
  from ophyd_async.core import StandardReadable
5
4
  from ophyd_async.epics.core import epics_signal_rw
dodal/devices/smargon.py CHANGED
@@ -3,7 +3,7 @@ from collections.abc import Collection, Generator
3
3
  from dataclasses import dataclass
4
4
  from enum import Enum
5
5
  from math import isclose
6
- from typing import NotRequired, TypedDict, cast
6
+ from typing import TypedDict, cast
7
7
 
8
8
  from bluesky import plan_stubs as bps
9
9
  from bluesky.protocols import Movable
@@ -12,6 +12,7 @@ from ophyd_async.core import (
12
12
  AsyncStatus,
13
13
  Device,
14
14
  StrictEnum,
15
+ set_and_wait_for_value,
15
16
  wait_for_value,
16
17
  )
17
18
  from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
@@ -104,15 +105,15 @@ class DeferMoves(StrictEnum):
104
105
  OFF = "Defer Off"
105
106
 
106
107
 
107
- class CombinedMove(TypedDict):
108
+ class CombinedMove(TypedDict, total=False):
108
109
  """A move on multiple axes at once using a deferred move"""
109
110
 
110
- x: NotRequired[float | None]
111
- y: NotRequired[float | None]
112
- z: NotRequired[float | None]
113
- omega: NotRequired[float | None]
114
- phi: NotRequired[float | None]
115
- chi: NotRequired[float | None]
111
+ x: float | None
112
+ y: float | None
113
+ z: float | None
114
+ omega: float | None
115
+ phi: float | None
116
+ chi: float | None
116
117
 
117
118
 
118
119
  class Smargon(XYZStage, Movable):
@@ -123,6 +124,8 @@ class Smargon(XYZStage, Movable):
123
124
  Robot loading can nudge these and lead to errors.
124
125
  """
125
126
 
127
+ DEFERRED_MOVE_SET_TIMEOUT = 5
128
+
126
129
  def __init__(self, prefix: str, name: str = ""):
127
130
  with self.add_children_as_readables():
128
131
  self.chi = Motor(prefix + "CHI")
@@ -161,15 +164,29 @@ class Smargon(XYZStage, Movable):
161
164
 
162
165
  @AsyncStatus.wrap
163
166
  async def set(self, value: CombinedMove):
167
+ """This will move all motion together in a deferred move.
168
+
169
+ Once defer_move is on, sets to any axis do not immediately move the axis. Instead
170
+ the setpoint will go to that value. Then, when defer_move is switched off all
171
+ axes will move at the same time. The put callbacks on the axes themselves will
172
+ only come back after the motion on that axis finished.
173
+ """
164
174
  await self.defer_move.set(DeferMoves.ON)
165
175
  try:
166
- tasks = []
167
- for k, v in value.items():
168
- if v is not None:
169
- tasks.append(getattr(self, k).set(v))
176
+ finished_moving = []
177
+ for motor_name, new_setpoint in value.items():
178
+ if new_setpoint is not None and isinstance(new_setpoint, int | float):
179
+ axis: Motor = getattr(self, motor_name)
180
+ await axis.check_motor_limit(
181
+ await axis.user_setpoint.get_value(), new_setpoint
182
+ )
183
+ put_completion = await set_and_wait_for_value(
184
+ axis.user_setpoint,
185
+ new_setpoint,
186
+ timeout=self.DEFERRED_MOVE_SET_TIMEOUT,
187
+ wait_for_set_completion=False,
188
+ )
189
+ finished_moving.append(put_completion)
170
190
  finally:
171
191
  await self.defer_move.set(DeferMoves.OFF)
172
- # The set() coroutines will not complete until after defer moves has been
173
- # switched back off so we cannot wait for them until this point.
174
- # see https://github.com/DiamondLightSource/dodal/issues/1315
175
- await asyncio.gather(*tasks)
192
+ await asyncio.gather(*finished_moving)
dodal/devices/thawer.py CHANGED
@@ -1,4 +1,4 @@
1
- from asyncio import Task, create_task, sleep
1
+ from asyncio import CancelledError, Task, create_task, sleep
2
2
 
3
3
  from bluesky.protocols import Movable, Stoppable
4
4
  from ophyd_async.core import (
@@ -11,6 +11,8 @@ from ophyd_async.core import (
11
11
  )
12
12
  from ophyd_async.epics.core import epics_signal_rw
13
13
 
14
+ from dodal.log import LOGGER
15
+
14
16
 
15
17
  class ThawingException(Exception):
16
18
  pass
@@ -24,19 +26,29 @@ class ThawingTimer(Device, Stoppable, Movable[float]):
24
26
 
25
27
  @AsyncStatus.wrap
26
28
  async def set(self, value: float):
27
- await self._control_signal_ref().set(OnOff.ON)
28
- if self._thawing_task and not self._thawing_task.done():
29
- raise ThawingException("Thawing task already in progress")
29
+ if self._thawing_task:
30
+ LOGGER.info("Thawing task already in progress, resetting timer")
31
+ self._thawing_task.cancel()
32
+ else:
33
+ LOGGER.info("Thawing started")
34
+ await self._control_signal_ref().set(OnOff.ON)
30
35
  self._thawing_task = create_task(sleep(value))
31
36
  try:
32
37
  await self._thawing_task
33
- finally:
38
+ except CancelledError:
39
+ LOGGER.info("Timer task cancelled.")
40
+ raise
41
+ else:
42
+ LOGGER.info("Thawing completed")
34
43
  await self._control_signal_ref().set(OnOff.OFF)
35
44
 
36
45
  @AsyncStatus.wrap
37
46
  async def stop(self, *args, **kwargs):
38
47
  if self._thawing_task:
39
48
  self._thawing_task.cancel()
49
+ self._thawing_task = None
50
+ LOGGER.info("Thawer stopped.")
51
+ await self._control_signal_ref().set(OnOff.OFF)
40
52
 
41
53
 
42
54
  class Thawer(StandardReadable, Stoppable):
dodal/devices/webcam.py CHANGED
@@ -51,6 +51,7 @@ class Webcam(StandardReadable, Triggerable):
51
51
  )
52
52
  try:
53
53
  data = await response.read()
54
+ Image.open(BytesIO(data)).verify()
54
55
  LOGGER.info(f"Saving webcam image from {self.url} to {file_path}")
55
56
  except Exception as e:
56
57
  LOGGER.warning(
dodal/testing/__init__.py CHANGED
@@ -0,0 +1,3 @@
1
+ from .setup import patch_all_motors, patch_motor
2
+
3
+ __all__ = ["patch_motor", "patch_all_motors"]
dodal/testing/setup.py ADDED
@@ -0,0 +1,67 @@
1
+ from contextlib import ExitStack
2
+
3
+ from ophyd_async.core import Device
4
+ from ophyd_async.epics.motor import Motor
5
+ from ophyd_async.testing import (
6
+ callback_on_mock_put,
7
+ set_mock_value,
8
+ )
9
+
10
+
11
+ def patch_motor(
12
+ motor: Motor,
13
+ initial_position: float = 0,
14
+ deadband: float = 0.001,
15
+ velocity: float = 3,
16
+ max_velocity: float = 5,
17
+ low_limit_travel: float = float("-inf"),
18
+ high_limit_travel: float = float("inf"),
19
+ ):
20
+ """
21
+ Patch a mock motor with sensible default values so that it can still be used in
22
+ tests and plans without running into errors as default values are zero.
23
+
24
+ Parameters:
25
+ motor: The mock motor to set mock values with.
26
+ initial_position: The default initial position of the motor to be set.
27
+ deadband: The tolerance between readback value and demand setpoint which the
28
+ motor is considered at position.
29
+ velocity: Requested move speed when the mock motor moves.
30
+ max_velocity: The maximum allowable velocity that can be set for the motor.
31
+ low_limit_travel: The lower limit that the motor can move to.
32
+ high_limit_travel: The higher limit that the motor can move to.
33
+ """
34
+ set_mock_value(motor.user_setpoint, initial_position)
35
+ set_mock_value(motor.user_readback, initial_position)
36
+ set_mock_value(motor.deadband, deadband)
37
+ set_mock_value(motor.motor_done_move, 1)
38
+ set_mock_value(motor.velocity, velocity)
39
+ set_mock_value(motor.max_velocity, max_velocity)
40
+ set_mock_value(motor.low_limit_travel, low_limit_travel)
41
+ set_mock_value(motor.high_limit_travel, high_limit_travel)
42
+ return callback_on_mock_put(
43
+ motor.user_setpoint,
44
+ lambda pos, *args, **kwargs: set_mock_value(motor.user_readback, pos),
45
+ )
46
+
47
+
48
+ def patch_all_motors(parent_device: Device):
49
+ """
50
+ Check all children of a device and patch any motors with mock values.
51
+
52
+ Parameters:
53
+ parent_device: The device that hold motor(s) as children.
54
+ """
55
+ motors = []
56
+
57
+ def recursively_find_motors(device: Device):
58
+ for _, child_device in device.children():
59
+ if isinstance(child_device, Motor):
60
+ motors.append(child_device)
61
+ recursively_find_motors(child_device)
62
+
63
+ recursively_find_motors(parent_device)
64
+ motor_patch_stack = ExitStack()
65
+ for motor in motors:
66
+ motor_patch_stack.enter_context(patch_motor(motor))
67
+ return motor_patch_stack
@@ -1,44 +0,0 @@
1
- """A small temporary device to set and read the filename template from the pilatus"""
2
-
3
- import re
4
-
5
- from ophyd_async.core import StandardReadable, derived_signal_r
6
- from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
7
-
8
-
9
- class PilatusMetadata(StandardReadable):
10
- def __init__(self, prefix: str, name: str = "") -> None:
11
- self.filename = epics_signal_rw(str, prefix + "cam1:FileName")
12
- self.template = epics_signal_r(str, prefix + "cam1:FileTemplate_RBV")
13
- self.filenumber = epics_signal_r(int, prefix + "cam1:FileNumber_RBV")
14
- with self.add_children_as_readables():
15
- self.filename_template = derived_signal_r(
16
- self._get_full_filename_template,
17
- filename=self.filename,
18
- filename_template=self.template,
19
- file_number=self.filenumber,
20
- )
21
- super().__init__(name)
22
-
23
- def _get_full_filename_template(
24
- self, filename: str, filename_template: str, file_number: int
25
- ) -> str:
26
- """
27
- Get the template file path by querying the detector PVs.
28
- Mirror the construction that the PPU does.
29
-
30
- Returns: A template string, with the image numbers replaced with '#'
31
- """
32
- # Exploit fact that passing negative numbers will put the - before the 0's
33
- expected_filename = str(
34
- filename_template % (filename, f"{file_number:05d}_", -9)
35
- )
36
- # Now, find the -09 part of this
37
- numberpart = re.search(r"(-0+9)", expected_filename)
38
- assert numberpart is not None
39
- template_fill = "#" * len(numberpart.group(0))
40
- return (
41
- expected_filename[: numberpart.start()]
42
- + template_fill
43
- + expected_filename[numberpart.end() :]
44
- )
@@ -1,37 +0,0 @@
1
- from contextlib import ExitStack
2
-
3
- from ophyd_async.core import Device
4
- from ophyd_async.epics.motor import Motor
5
- from ophyd_async.testing import (
6
- callback_on_mock_put,
7
- set_mock_value,
8
- )
9
-
10
-
11
- def patch_motor(motor: Motor, initial_position=0):
12
- set_mock_value(motor.user_setpoint, initial_position)
13
- set_mock_value(motor.user_readback, initial_position)
14
- set_mock_value(motor.deadband, 0.001)
15
- set_mock_value(motor.motor_done_move, 1)
16
- set_mock_value(motor.velocity, 3)
17
- set_mock_value(motor.max_velocity, 5)
18
- return callback_on_mock_put(
19
- motor.user_setpoint,
20
- lambda pos, *args, **kwargs: set_mock_value(motor.user_readback, pos),
21
- )
22
-
23
-
24
- def patch_all_motors(parent_device: Device):
25
- motors = []
26
-
27
- def recursively_find_motors(device: Device):
28
- for _, child_device in device.children():
29
- if isinstance(child_device, Motor):
30
- motors.append(child_device)
31
- recursively_find_motors(child_device)
32
-
33
- recursively_find_motors(parent_device)
34
- motor_patch_stack = ExitStack()
35
- for motor in motors:
36
- motor_patch_stack.enter_context(patch_motor(motor))
37
- return motor_patch_stack