dls-dodal 1.29.0__py3-none-any.whl → 1.29.3__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.1
2
2
  Name: dls-dodal
3
- Version: 1.29.0
3
+ Version: 1.29.3
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>
6
6
  License: Apache License
@@ -242,9 +242,11 @@ Requires-Dist: pytest-asyncio ; extra == 'dev'
242
242
  Requires-Dist: pytest-cov ; extra == 'dev'
243
243
  Requires-Dist: pytest-random-order ; extra == 'dev'
244
244
  Requires-Dist: ruff ; extra == 'dev'
245
- Requires-Dist: sphinx-autobuild ==2024.2.4 ; extra == 'dev'
245
+ Requires-Dist: sphinx-autobuild ; extra == 'dev'
246
246
  Requires-Dist: sphinx-copybutton ; extra == 'dev'
247
+ Requires-Dist: sphinxcontrib-mermaid ; extra == 'dev'
247
248
  Requires-Dist: sphinx-design ; extra == 'dev'
249
+ Requires-Dist: sphinx-autodoc-typehints ; extra == 'dev'
248
250
  Requires-Dist: tox-direct ; extra == 'dev'
249
251
  Requires-Dist: types-requests ; extra == 'dev'
250
252
  Requires-Dist: types-mock ; extra == 'dev'
@@ -1,9 +1,9 @@
1
1
  dodal/__init__.py,sha256=y-VRpfiX-Lm5nchB9N0VfMy_6dwFqVxpSn5SiAQql9I,114
2
2
  dodal/__main__.py,sha256=kP2S2RPitnOWpNGokjZ1Yq-1umOtp5sNOZk2B3tBPLM,111
3
- dodal/_version.py,sha256=IpRR4M6LqazbEQMvyYdrRT7AijfNAsCfLKuNjjsDX4s,413
3
+ dodal/_version.py,sha256=MFlkKJKBPRaG5o-APjK5uWe00dvrrzhYEjQcU5M7B2I,413
4
4
  dodal/adsim.py,sha256=OW2dcS7ciD4Yq9WFw4PN_c5Bwccrmu7R-zr-u6ZCbQM,497
5
5
  dodal/cli.py,sha256=z0UBESrNrq6Kq4rttp4uHcwS1fnOnRkKBRDHSriPpGY,2058
6
- dodal/log.py,sha256=grK5-c-V6UjMERwDqYPdKbc_BpycrNb8hP0uteLOVCY,8296
6
+ dodal/log.py,sha256=dfo1rfYrGG8oIm2HkNxaa_ldVs4vJKtgWSoKe1Z_Xno,8533
7
7
  dodal/utils.py,sha256=aH-W94t6NFOoGHZ7awbUKY8_k7qIYDourCFs3MKIjjA,10024
8
8
  dodal/beamline_specific_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  dodal/beamline_specific_utils/i03.py,sha256=Ixe1anFQl-kwRJubmQx28TIW4Zw8qDxpElNNNapWQHI,396
@@ -13,9 +13,9 @@ dodal/beamlines/i03.py,sha256=J-4GawTxAhrYDQZh87n11L9ehTgObEQ1Lz2PueXCWbc,16892
13
13
  dodal/beamlines/i04.py,sha256=JOyNcUnC3wva4no2MHKp6b8gOKAcQXL_c4cBo7oneVs,13034
14
14
  dodal/beamlines/i04_1.py,sha256=KDxSUQNhIs_NFiRaLY-Jiory0DeN7Y0ErvGuoTrwCDU,4731
15
15
  dodal/beamlines/i20_1.py,sha256=MaPgONHqpoZuBtkiKEzYtViJnKBM2_ekeP4OdbmuXHE,1158
16
- dodal/beamlines/i22.py,sha256=toBwXJiAFbie_puNJzrV1gSUD1FRO2KT9RastqhPNMw,9836
17
- dodal/beamlines/i23.py,sha256=iEFkrA4sPQsRLGAmeD263jPMX4u2SF1NK4_KYqnVwow,1402
18
- dodal/beamlines/i24.py,sha256=LIe8tu_ZJsoeQH7tYRZHSvqQ9C9zBGOXO6rb1DPCYjQ,4595
16
+ dodal/beamlines/i22.py,sha256=3VFdA4Wc7O40-64lwUtUBIN23fH4JVNbLKJ1JLjy9as,9870
17
+ dodal/beamlines/i23.py,sha256=2j5qLoqE_hg9ETHqNkOVu7LLkVB8qalgXeORnVYKN_I,1075
18
+ dodal/beamlines/i24.py,sha256=dCMQGcBZ6ADZ6_rEDFcV2BPHGKBC9iVFvfxewxVts4k,6111
19
19
  dodal/beamlines/p38.py,sha256=TC78u4GwEnj6X0r5cnHhqNdBbbDRnh8lY8pEucBZjEU,8006
20
20
  dodal/beamlines/p45.py,sha256=TNIkC-SBfj0ayZtlLLXW9xCSi5CzJkO8XpAMIo8fjao,2957
21
21
  dodal/common/__init__.py,sha256=ZC4ICKUDB0BDxRaVy8nmqclVmDBne-dPtk6UJsoFq6I,258
@@ -40,32 +40,33 @@ dodal/devices/cryostream.py,sha256=6MU4rXIOL33C-8F3DVfAtv0ZnwiysTtawjkeePd5IrQ,3
40
40
  dodal/devices/dcm.py,sha256=vfyGYDzfSwTiNqlzkfNjkrL-Q1hNVSgJddvJ5Un_lvg,1610
41
41
  dodal/devices/eiger.py,sha256=NE4tHdqgUZpUxJLQbd5lLUIHZcpeotppexJGlDNByzM,13868
42
42
  dodal/devices/eiger_odin.py,sha256=U5Byb7uNwDdNscBRp7yBYQrsjKrKXl2l5WdSpL09lAw,6980
43
- dodal/devices/fast_grid_scan.py,sha256=qp0jdzhTjkDtaf7ngdPcegcFLlp1EmWUDbU_knCBDpc,13438
43
+ dodal/devices/fast_grid_scan.py,sha256=BEj96j78r60JPPJoOMP-XXG-_9yURFTuu-pp2LcqQmY,12452
44
44
  dodal/devices/fluorescence_detector_motion.py,sha256=RrXfPmJzWnAjcjp9u0AnJEfjvWPMKburVTySB2hxYbw,181
45
45
  dodal/devices/flux.py,sha256=RtPStHw7Mad0igVKntKWVZfuZn2clokVJqH14HLix6M,198
46
46
  dodal/devices/focusing_mirror.py,sha256=aRqBkE3OgaXpH6lP3v1VbSYgHsMMbSsPPXzeyAGf_Pg,6435
47
+ dodal/devices/hutch_shutter.py,sha256=nZ3gRbYIVJsXLlpZMWT4UEYUFFQP1MwMe8Oy304QsqE,3360
47
48
  dodal/devices/ipin.py,sha256=OGMXwAE4KDDonZRPFkUmR9Vsk6X4Ox-hEvPT5drP-mQ,208
48
49
  dodal/devices/linkam3.py,sha256=TPhiQ1D9i_HIlKHAlfnVfX7H6aPOAeXPEJLdmvwdKWQ,3776
49
50
  dodal/devices/logging_ophyd_device.py,sha256=xw4lbyqq5_ehESGterVEfubJsBiJTWvBp5b9k62gSkg,666
50
- dodal/devices/motors.py,sha256=T0wKGpRKhgduAQj3jE8CGmxy3cWMAFl9_glyjVZwjnA,1473
51
+ dodal/devices/motors.py,sha256=16ID2jFJ35h6ZrFp76nJG_oQg6uDrupgcbvcbmjlc7c,300
51
52
  dodal/devices/p45.py,sha256=jzBW2fGRhIbGzSRs5Fgupxro6aqE611n1RTcrTTG-yY,1047
52
53
  dodal/devices/qbpm1.py,sha256=OY7-WbdxMiLGUK8Z57ezwqSXbHxoPP-y3GvBgj9kgMA,220
53
- dodal/devices/robot.py,sha256=RcSqBPMMUpWyP7LoyifatII5GWup-vFYZutniM60m_k,4231
54
+ dodal/devices/robot.py,sha256=5WQ9kF5m8xhHhipBycsycDV0-_2IBNBkcwuSWP-9-1I,4337
54
55
  dodal/devices/s4_slit_gaps.py,sha256=j3kgF9WfGFaU9xdUuiAh-QqI5u_vhiAftaDVINt91SM,243
55
56
  dodal/devices/scatterguard.py,sha256=0qnvhoo3RjLsrxVgIoDJpryqunlgMVgaTsoyKRC2g4Y,331
56
57
  dodal/devices/scintillator.py,sha256=4Dej1a6HRom9GRwTDsaTKGfvloP20POUqIeHqsI8-R8,184
57
58
  dodal/devices/slits.py,sha256=URru9VN2N19KqeUPDZaBmyKYn0_JJiE0Vko4sZpfsl8,601
58
- dodal/devices/smargon.py,sha256=ml96h7E1C31qPo8jocAepSouIVXgpIR0vuMF99nZjqM,2964
59
+ dodal/devices/smargon.py,sha256=Ds8QFqK3ljbTxalqkQ6clpArj4u4hu9d4vrt97Fzdf4,4693
59
60
  dodal/devices/status.py,sha256=TuUGidZ4Ar-WCRc_sX0wn58DmL6brj1pMr8rNF5Z6VU,1198
60
61
  dodal/devices/synchrotron.py,sha256=QtTufJA_fCaBawHougSc7nxwu240oX46_y0P-4qIW8o,1960
61
62
  dodal/devices/tetramm.py,sha256=XriN-zBFVnHxhnTbphSPIZcxEbdWBTbw2g_ulUBl4bw,8538
62
- dodal/devices/thawer.py,sha256=L5OYSdzGvx6dIkGgcTbITAbFAm0OKEVVqYBb4MPstOg,382
63
+ dodal/devices/thawer.py,sha256=hIdZOzCNloY7CtSvdE2gk4vCMMoOtaIA4dPH_k0OwFg,1527
63
64
  dodal/devices/turbo_slit.py,sha256=W3ZRIqDhq4iMhr5GcIiWvl2U1GaPtGanqkL7upQOZTY,1132
64
65
  dodal/devices/undulator.py,sha256=kn84MQpuBHtQj7H7HeBoAYKXu5buGKvTgs3tf2gdEdw,2074
65
66
  dodal/devices/undulator_dcm.py,sha256=TC9fO55r1YIG_88PPbGGtzfjcRJcaoC2ny51JiDOEX4,5199
66
67
  dodal/devices/webcam.py,sha256=FXYcxQdOOCRIMAf8jMWlDVAhSEs4ycGCnoODvHb-apM,1554
67
68
  dodal/devices/xbpm_feedback.py,sha256=8QHYKHo9ksZo30olbFM-tHpCHcJRFozgHKVJijv3Gck,1986
68
- dodal/devices/zebra.py,sha256=XE2oEm6kLkXd86ev4dYP8bh3tI9Fesb1SmU61RSMHBA,9082
69
+ dodal/devices/zebra.py,sha256=9Zkq5I3-gcP6qfDBnPEAtFU4QJ-VJyp7cHvB79ZfLHk,9186
69
70
  dodal/devices/zebra_controlled_shutter.py,sha256=MqX4KE6w0FliZRDBltswcLCNSsp6vQrD_iBY640IljI,1094
70
71
  dodal/devices/areadetector/__init__.py,sha256=8IwLxuZMW0MOJpJp_ZDdlaE20hrtsH_PXWGaKgMiYs4,240
71
72
  dodal/devices/areadetector/adaravis.py,sha256=pwbmmnakarjhD59XoyAIXJdakS-nqDG09Xmwq17AVw4,3787
@@ -84,13 +85,13 @@ dodal/devices/i20_1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
84
85
  dodal/devices/i22/dcm.py,sha256=Kzyd_qFg8KVhRsgfTQVOpghESE8yIOgACKa0Fv9NaZI,6270
85
86
  dodal/devices/i22/fswitch.py,sha256=AdYtnkCBuhivyJGZqelg_7sjB2pHN7vl1JTtlO4vHo4,3061
86
87
  dodal/devices/i22/nxsas.py,sha256=ky7v9UZ1UQFsm5hI0wD9OXG-fTKFLj2wJjB7wADxKpw,5655
87
- dodal/devices/i23/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
88
- dodal/devices/i23/gonio.py,sha256=cxqD2Kd578c4-K4h2sHdJjoap2gPGC46Qr4SPudHLTs,864
89
- dodal/devices/i24/I24_detector_motion.py,sha256=bKbb44Qs24oguwJ780N4e5XGNtka_3ZZCGGq6BQu99Y,229
88
+ dodal/devices/i24/I24_detector_motion.py,sha256=Joqr1orgeNvRS7n01bjaO-4Yu4obb8fnKaWHQfjPX14,365
90
89
  dodal/devices/i24/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
- dodal/devices/i24/dual_backlight.py,sha256=5Xkkt4VqS-G1QLDmBJNHs-LNVYJ_aTso_ev8QNDJiis,1367
90
+ dodal/devices/i24/aperture.py,sha256=kKfHli5oKp-j-qZhZoXTRK81SAUNyhpI6VRvtw0SkZA,850
91
+ dodal/devices/i24/beamstop.py,sha256=28hQowTvgN5Zw38tkDh32h2ceyN-2GE8bAaGPvDOt5U,1234
92
+ dodal/devices/i24/dual_backlight.py,sha256=Th-RKr28aFxE8LCT_mdN9KkRIVw0BHLGKkI0ienfRZU,2049
92
93
  dodal/devices/i24/i24_vgonio.py,sha256=Igqs7687z6lyhGVeJEDtDmPachYxU48MUH2BF0RpK9Q,461
93
- dodal/devices/i24/pmac.py,sha256=CdNLzRiOz2jFoZ80WQTdlHiYoDJeQWdqdKDkSKkCEkI,3872
94
+ dodal/devices/i24/pmac.py,sha256=pN54myYvzqPl7iW0Vsp59J1EiV_gtn0xQGwbsKJpiYE,3876
94
95
  dodal/devices/oav/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
95
96
  dodal/devices/oav/grid_overlay.py,sha256=FRtjcFd420XY8MEQ9sWedL0i4pK-KUJOSxh2C5zM3PA,5232
96
97
  dodal/devices/oav/microns_for_zoom_levels.json,sha256=5PA71RzldFTp0eTUGPmov0MjxHe583mzvfor5f3thXI,1208
@@ -104,7 +105,7 @@ dodal/devices/oav/pin_image_recognition/manual_test.py,sha256=h1Rto6ZDCB3jWhjSy9
104
105
  dodal/devices/oav/pin_image_recognition/utils.py,sha256=-7-Zs-331UVTq_AZrfdF-zwZdmMn7eitTkBSqnBrxnk,8620
105
106
  dodal/devices/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
106
107
  dodal/devices/util/adjuster_plans.py,sha256=2AYaywQP_LbA2KJ6Op3cok8GoRtj696utrSSDfaJtBY,875
107
- dodal/devices/util/epics_util.py,sha256=FSiTgBhm4id1nILM3UUD7V3eDjJM4DEMIG_wEwzM8Tk,4441
108
+ dodal/devices/util/epics_util.py,sha256=eQr-ImBnADpBL_6XWr3_q9yuMe55Lu0h3j9L1fG4Jws,4714
108
109
  dodal/devices/util/lookup_tables.py,sha256=Up-0BlARt79TIEM76SkDyn9LtTFLxPUcaEPZv6D6bws,2141
109
110
  dodal/devices/util/motor_utils.py,sha256=pNY-aUk9LxaIWeDr5rpMS6udiB9j19wcCXkNDLp1uA0,257
110
111
  dodal/devices/xspress3/xspress3.py,sha256=29elzI3JtceryKeMWXhcP9nWl0tlSdnTZhltCitet6A,4668
@@ -115,9 +116,9 @@ dodal/devices/zocalo/zocalo_results.py,sha256=U4Vk4OF-eL8w0BR-fbw3k4jyRo6G3Ywaf8
115
116
  dodal/parameters/experiment_parameter_base.py,sha256=O7JamfuJ5cYHkPf9tsHJPqn-OMHTAGouigvM1cDFehE,313
116
117
  dodal/plans/check_topup.py,sha256=Pj6Eu8fa6nvoW4awrMxvzE_ftpLfYz8bN0QDLRw0Yuk,2989
117
118
  dodal/plans/data_session_metadata.py,sha256=QNx9rb1EfGBHb21eFekIi7KjNhC0PL-SVKBCggDuNeg,1650
118
- dls_dodal-1.29.0.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
119
- dls_dodal-1.29.0.dist-info/METADATA,sha256=ydEXpqy3OYgQ-oGOFOzOFk_D4Xhr9N7zUQfvWp51Evo,16842
120
- dls_dodal-1.29.0.dist-info/WHEEL,sha256=cpQTJ5IWu9CdaPViMhC9YzF8gZuS5-vlfoFihTBC86A,91
121
- dls_dodal-1.29.0.dist-info/entry_points.txt,sha256=wpzz9FsTiYxI8OBwLKX9V9ResLwThBSmtRMcPwII0FA,46
122
- dls_dodal-1.29.0.dist-info/top_level.txt,sha256=xIozdmZk_wmMV4wugpq9-6eZs0vgADNUKz3j2UAwlhc,6
123
- dls_dodal-1.29.0.dist-info/RECORD,,
119
+ dls_dodal-1.29.3.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
120
+ dls_dodal-1.29.3.dist-info/METADATA,sha256=WmlVBYCSTsRn07vPGXJa_FF8x_djiThku9KSaCay3A8,16942
121
+ dls_dodal-1.29.3.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
122
+ dls_dodal-1.29.3.dist-info/entry_points.txt,sha256=wpzz9FsTiYxI8OBwLKX9V9ResLwThBSmtRMcPwII0FA,46
123
+ dls_dodal-1.29.3.dist-info/top_level.txt,sha256=xIozdmZk_wmMV4wugpq9-6eZs0vgADNUKz3j2UAwlhc,6
124
+ dls_dodal-1.29.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.1.0)
2
+ Generator: setuptools (70.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
dodal/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.29.0'
16
- __version_tuple__ = version_tuple = (1, 29, 0)
15
+ __version__ = version = '1.29.3'
16
+ __version_tuple__ = version_tuple = (1, 29, 3)
dodal/beamlines/i22.py CHANGED
@@ -360,6 +360,7 @@ def oav(
360
360
  x_pixel_size=(3.45e-3, "mm"), # Double check this figure
361
361
  y_pixel_size=(3.45e-3, "mm"),
362
362
  description="AVT Mako G-507B",
363
+ distance=(-1.0, "m"),
363
364
  ),
364
365
  directory_provider=get_directory_provider(),
365
366
  )
dodal/beamlines/i23.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from dodal.common.beamlines.beamline_utils import device_instantiation
2
2
  from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
3
- from dodal.devices.i23.gonio import Gonio
4
3
  from dodal.devices.oav.pin_image_recognition import PinTipDetection
5
4
  from dodal.log import set_beamline as set_log_beamline
6
5
  from dodal.utils import get_beamline_name, get_hostname, skip_device
@@ -19,17 +18,6 @@ def _is_i23_machine():
19
18
  return hostname.startswith("i23-ws") or hostname.startswith("i23-control")
20
19
 
21
20
 
22
- def gonio(wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False) -> Gonio:
23
- """Get the i23 goniometer device"""
24
- return device_instantiation(
25
- Gonio,
26
- "Gonio",
27
- "-MO-GONIO-01:",
28
- wait_for_connection,
29
- fake_with_ophyd_sim,
30
- )
31
-
32
-
33
21
  @skip_device(lambda: not _is_i23_machine())
34
22
  def oav_pin_tip_detection(
35
23
  wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
dodal/beamlines/i24.py CHANGED
@@ -2,11 +2,15 @@ from dodal.common.beamlines.beamline_utils import BL, device_instantiation
2
2
  from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
3
3
  from dodal.devices.detector import DetectorParams
4
4
  from dodal.devices.eiger import EigerDetector
5
+ from dodal.devices.hutch_shutter import HutchShutter
6
+ from dodal.devices.i24.aperture import Aperture
7
+ from dodal.devices.i24.beamstop import Beamstop
5
8
  from dodal.devices.i24.dual_backlight import DualBacklight
6
9
  from dodal.devices.i24.I24_detector_motion import DetectorMotion
7
10
  from dodal.devices.i24.i24_vgonio import VGonio
8
11
  from dodal.devices.i24.pmac import PMAC
9
- from dodal.devices.oav.oav_detector import OAV, OAVConfigParams
12
+ from dodal.devices.oav.oav_detector import OAV
13
+ from dodal.devices.oav.oav_parameters import OAVConfigParams
10
14
  from dodal.devices.zebra import Zebra
11
15
  from dodal.log import set_beamline as set_log_beamline
12
16
  from dodal.utils import get_beamline_name, skip_device
@@ -21,6 +25,32 @@ set_log_beamline(BL)
21
25
  set_utils_beamline(BL)
22
26
 
23
27
 
28
+ def aperture(
29
+ wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
30
+ ) -> Aperture:
31
+ """Get the i24 aperture device, instantiate it if it hasn't already been.
32
+ If this is called when already instantiated in i24, it will return the existing object.
33
+ """
34
+ return device_instantiation(
35
+ Aperture, "aperture", "-AL-APTR-01:", wait_for_connection, fake_with_ophyd_sim
36
+ )
37
+
38
+
39
+ def beamstop(
40
+ wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
41
+ ) -> Beamstop:
42
+ """Get the i24 beamstop device, instantiate it if it hasn't already been.
43
+ If this is called when already instantiated in i24, it will return the existing object.
44
+ """
45
+ return device_instantiation(
46
+ Beamstop,
47
+ "beamstop",
48
+ "-MO-BS-01:",
49
+ wait_for_connection,
50
+ fake_with_ophyd_sim,
51
+ )
52
+
53
+
24
54
  def backlight(
25
55
  wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
26
56
  ) -> DualBacklight:
@@ -134,3 +164,19 @@ def zebra(wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False) -
134
164
  wait_for_connection,
135
165
  fake_with_ophyd_sim,
136
166
  )
167
+
168
+
169
+ @skip_device(lambda: BL == "s24")
170
+ def shutter(
171
+ wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
172
+ ) -> HutchShutter:
173
+ """Get the i24 hutch shutter device, instantiate it if it hasn't already been.
174
+ If this is called when already instantiated, it will return the existing object.
175
+ """
176
+ return device_instantiation(
177
+ HutchShutter,
178
+ "shutter",
179
+ "-PS-SHTR-01:",
180
+ wait_for_connection,
181
+ fake_with_ophyd_sim,
182
+ )
@@ -23,7 +23,6 @@ from ophyd_async.epics.signal import (
23
23
  from pydantic import validator
24
24
  from pydantic.dataclasses import dataclass
25
25
 
26
- from dodal.devices.motors import XYZLimitBundle
27
26
  from dodal.log import LOGGER
28
27
  from dodal.parameters.experiment_parameter_base import AbstractExperimentWithBeamParams
29
28
 
@@ -112,35 +111,6 @@ class GridScanParamsCommon(AbstractExperimentWithBeamParams):
112
111
  def _get_z_axis(cls, z_axis: GridAxis, values: dict[str, Any]) -> GridAxis:
113
112
  return GridAxis(values["z2_start"], values["z_step_size"], values["z_steps"])
114
113
 
115
- def is_valid(self, limits: XYZLimitBundle) -> bool:
116
- """
117
- Validates scan parameters
118
-
119
- :param limits: The motor limits against which to validate
120
- the parameters
121
- :return: True if the scan is valid
122
- """
123
- x_in_limits = limits.x.is_within(self.x_axis.start) and limits.x.is_within(
124
- self.x_axis.end
125
- )
126
- y_in_limits = limits.y.is_within(self.y_axis.start) and limits.y.is_within(
127
- self.y_axis.end
128
- )
129
-
130
- first_grid_in_limits = (
131
- x_in_limits and y_in_limits and limits.z.is_within(self.z1_start)
132
- )
133
-
134
- z_in_limits = limits.z.is_within(self.z_axis.start) and limits.z.is_within(
135
- self.z_axis.end
136
- )
137
-
138
- second_grid_in_limits = (
139
- x_in_limits and z_in_limits and limits.y.is_within(self.y2_start)
140
- )
141
-
142
- return first_grid_in_limits and second_grid_in_limits
143
-
144
114
  def get_num_images(self):
145
115
  return self.x_steps * self.y_steps + self.x_steps * self.z_steps
146
116
 
@@ -0,0 +1,93 @@
1
+ from enum import Enum
2
+
3
+ from bluesky.protocols import Movable
4
+ from ophyd_async.core import (
5
+ DEFAULT_TIMEOUT,
6
+ AsyncStatus,
7
+ StandardReadable,
8
+ wait_for_value,
9
+ )
10
+ from ophyd_async.epics.signal import epics_signal_r, epics_signal_w
11
+
12
+ HUTCH_SAFE_FOR_OPERATIONS = 0 # Hutch is locked and can't be entered
13
+
14
+
15
+ class ShutterNotSafeToOperateError(Exception):
16
+ pass
17
+
18
+
19
+ class ShutterDemand(str, Enum):
20
+ OPEN = "Open"
21
+ CLOSE = "Close"
22
+ RESET = "Reset"
23
+
24
+
25
+ class ShutterState(str, Enum):
26
+ FAULT = "Fault"
27
+ OPEN = "Open"
28
+ OPENING = "Opening"
29
+ CLOSED = "Closed"
30
+ CLOSING = "Closing"
31
+
32
+
33
+ class HutchInterlock(StandardReadable):
34
+ """Device to check the interlock status of the hutch."""
35
+
36
+ def __init__(self, bl_prefix: str, name: str = "") -> None:
37
+ self.status = epics_signal_r(float, bl_prefix + "-PS-IOC-01:M14:LOP")
38
+ super().__init__(name)
39
+
40
+ # TODO replace with read
41
+ # See https://github.com/DiamondLightSource/dodal/issues/651
42
+ async def shutter_safe_to_operate(self) -> bool:
43
+ """If the status value is 0, hutch has been searched and locked and it is safe \
44
+ to operate the shutter.
45
+ If the status value is not 0 (usually set to 7), the hutch is open and the \
46
+ shutter should not be in use.
47
+ """
48
+ interlock_state = await self.status.get_value()
49
+ return interlock_state == HUTCH_SAFE_FOR_OPERATIONS
50
+
51
+
52
+ class HutchShutter(StandardReadable, Movable):
53
+ """Device to operate the hutch shutter.
54
+
55
+ When a demand is sent, the device should first check the hutch status \
56
+ and raise an error if it's not interlocked (searched and locked), meaning it's not \
57
+ safe to operate the shutter.
58
+
59
+ If the requested shutter position is "Open", the shutter control PV should first \
60
+ go to "Reset" and then move to "Open". This is because before opening the hutch \
61
+ shutter, the interlock status PV (`-PS-SHTR-01:ILKSTA`) will show as `failed` until \
62
+ the hutch shutter is reset. This will set the interlock status to `OK`, allowing \
63
+ for shutter operations. Until this step is done, the hutch shutter can't be opened.
64
+ The reset is not needed for closing the shutter.
65
+ """
66
+
67
+ def __init__(self, prefix: str, name: str = "") -> None:
68
+ self.control = epics_signal_w(ShutterDemand, prefix + "CON")
69
+ self.status = epics_signal_r(ShutterState, prefix + "STA")
70
+
71
+ bl_prefix = prefix.split("-")[0]
72
+ self.interlock = HutchInterlock(bl_prefix)
73
+
74
+ super().__init__(name)
75
+
76
+ @AsyncStatus.wrap
77
+ async def set(self, position_demand: ShutterDemand):
78
+ interlock_state = await self.interlock.shutter_safe_to_operate()
79
+ if not interlock_state:
80
+ raise ShutterNotSafeToOperateError(
81
+ "The hutch has not been locked, not operating shutter."
82
+ )
83
+ if position_demand == ShutterDemand.OPEN:
84
+ await self.control.set(ShutterDemand.RESET, wait=True)
85
+ await self.control.set(position_demand, wait=True)
86
+ return await wait_for_value(
87
+ self.status, match=ShutterState.OPEN, timeout=DEFAULT_TIMEOUT
88
+ )
89
+ else:
90
+ await self.control.set(position_demand, wait=True)
91
+ return await wait_for_value(
92
+ self.status, match=ShutterState.CLOSED, timeout=DEFAULT_TIMEOUT
93
+ )
@@ -1,9 +1,12 @@
1
- from ophyd import Component as Cpt
2
- from ophyd import Device, EpicsMotor
1
+ from ophyd_async.core import StandardReadable
2
+ from ophyd_async.epics.motion import Motor
3
3
 
4
4
 
5
- class DetectorMotion(Device):
5
+ class DetectorMotion(StandardReadable):
6
6
  """Physical motion for detector travel"""
7
7
 
8
- y = Cpt(EpicsMotor, "Y") # Vertical
9
- z = Cpt(EpicsMotor, "Z") # Beam
8
+ def __init__(self, prefix: str, name: str = "") -> None:
9
+ self.y = Motor(prefix + "Y") # Vertical
10
+ self.z = Motor(prefix + "Z") # Beam
11
+
12
+ super().__init__(name)
@@ -0,0 +1,29 @@
1
+ from enum import Enum
2
+
3
+ from ophyd_async.core import StandardReadable
4
+ from ophyd_async.epics.motion import Motor
5
+ from ophyd_async.epics.signal import epics_signal_rw
6
+
7
+
8
+ class AperturePositions(str, Enum):
9
+ IN = "In"
10
+ OUT = "Out"
11
+ ROBOT = "Robot"
12
+ MANUAL = "Manual Mounting"
13
+
14
+
15
+ class Aperture(StandardReadable):
16
+ """Device to trigger the aperture motor move on I24.
17
+
18
+ The aperture positioner has 4 possible positions: In, Out, Robot and Manual.
19
+
20
+ When a position is selected, the x motor is moved.
21
+ The position of the y motor is calibrated at start up and is not changed.
22
+ """
23
+
24
+ def __init__(self, prefix: str, name: str = "") -> None:
25
+ self.x = Motor(prefix + "X")
26
+ self.y = Motor(prefix + "Y")
27
+
28
+ self.position = epics_signal_rw(AperturePositions, prefix + "MP:SELECT")
29
+ super().__init__(name)
@@ -0,0 +1,38 @@
1
+ from enum import Enum
2
+
3
+ from ophyd_async.core import StandardReadable
4
+ from ophyd_async.epics.motion import Motor
5
+ from ophyd_async.epics.signal import epics_signal_rw
6
+
7
+
8
+ class BeamstopPositions(str, Enum):
9
+ CHECK_BEAM = "CheckBeam"
10
+ DATA_COLLECTION = "Data Collection"
11
+ DATA_COLLECTION_FAR = "Data Collection Far"
12
+ TRAY_MOUNT = "Tray Mount"
13
+ ROTATABLE = "Rotatable"
14
+ ROBOT = "Robot"
15
+
16
+
17
+ class Beamstop(StandardReadable):
18
+ """Device to move the beamstop.
19
+
20
+ The positioner moves the x,y,z motors when a position is selected.
21
+ The additional y_rotation motor is independent of the positioner and can to be moved
22
+ on its own as needed.
23
+
24
+ WARNING. Before moving the y_rotation motor away from 0, it is important to make sure
25
+ that the backlight is in the "OUT" position to avoid a collision.
26
+ See also https://github.com/DiamondLightSource/dodal/issues/646.
27
+ """
28
+
29
+ def __init__(self, prefix: str, name: str = "") -> None:
30
+ self.x = Motor(prefix + "X")
31
+ self.y = Motor(prefix + "Y")
32
+ self.z = Motor(prefix + "Z")
33
+
34
+ self.y_rotation = Motor(prefix + "ROTY")
35
+
36
+ self.pos_select = epics_signal_rw(BeamstopPositions, prefix + "MP:SELECT")
37
+
38
+ super().__init__(name)
@@ -1,14 +1,36 @@
1
- from ophyd import Component, Device, EpicsSignal, StatusBase
1
+ from enum import Enum
2
2
 
3
+ from ophyd_async.core import AsyncStatus, StandardReadable
4
+ from ophyd_async.epics.signal import epics_signal_rw
3
5
 
4
- class BacklightPositioner(Device):
6
+
7
+ class BacklightPositions(str, Enum):
8
+ OUT = "Out"
9
+ IN = "In"
10
+ LOAD_CHECK = "LoadCheck"
11
+ OAV2 = "OAV2"
12
+ DIODE = "Diode"
13
+
14
+
15
+ class LEDStatus(str, Enum):
16
+ OFF = "OFF"
17
+ ON = "ON"
18
+
19
+
20
+ class BacklightPositioner(StandardReadable):
5
21
  """Device to control the backlight position."""
6
22
 
7
- # String description of the backlight position e.g. "In", "OAV2"
8
- pos_level = Component(EpicsSignal, "MP:SELECT")
23
+ def __init__(self, prefix: str, name: str = "") -> None:
24
+ # Enum description of the backlight position e.g. "In", "OAV2"
25
+ self.pos_level = epics_signal_rw(BacklightPositions, prefix + "MP:SELECT")
26
+ super().__init__(name)
27
+
28
+ @AsyncStatus.wrap
29
+ async def set(self, position: BacklightPositions):
30
+ await self.pos_level.set(position, wait=True)
9
31
 
10
32
 
11
- class DualBacklight(Device):
33
+ class DualBacklight(StandardReadable):
12
34
  """
13
35
  Device to trigger the dual backlight on I24.
14
36
  This device is made up by two LEDs:
@@ -17,27 +39,23 @@ class DualBacklight(Device):
17
39
 
18
40
  To set the position for LED1:
19
41
  b = DualBacklight(name="backlight)
20
- b.pos1.pos_level.set("OAV2")
21
-
22
- To see get the available position values for LED1:
23
- b.pos1.alowed_backlight_positions
42
+ b.backlight_position.set("OAV2")
24
43
 
25
44
  Note that the two LED are independently switched on and off. When LED1 is
26
45
  in "Out" position (switched off), LED2 might still be on.
27
46
  """
28
47
 
29
- OUT = "Out"
30
- IN = "In"
31
-
32
- led1 = Component(EpicsSignal, "-DI-LED-01:TOGGLE")
33
- pos1 = Component(BacklightPositioner, "-MO-BL-01:")
48
+ def __init__(self, prefix: str, name: str = "") -> None:
49
+ self.backlight_state = epics_signal_rw(LEDStatus, prefix + "-DI-LED-01:TOGGLE")
50
+ self.backlight_position = BacklightPositioner(prefix + "-MO-BL-01:", name)
34
51
 
35
- led2 = Component(EpicsSignal, "-DI-LED-02:TOGGLE")
52
+ self.frontlight_state = epics_signal_rw(LEDStatus, prefix + "-DI-LED-02:TOGGLE")
53
+ super().__init__(name)
36
54
 
37
- def set(self, position: str) -> StatusBase:
38
- status = self.pos1.pos_level.set(position)
39
- if position == self.OUT:
40
- status &= self.led1.set("OFF")
55
+ @AsyncStatus.wrap
56
+ async def set(self, position: BacklightPositions):
57
+ await self.backlight_position.set(position)
58
+ if position == BacklightPositions.OUT:
59
+ await self.backlight_state.set(LEDStatus.OFF, wait=True)
41
60
  else:
42
- status &= self.led1.set("ON")
43
- return status
61
+ await self.backlight_state.set(LEDStatus.ON, wait=True)
dodal/devices/i24/pmac.py CHANGED
@@ -118,7 +118,7 @@ class PMAC(StandardReadable):
118
118
 
119
119
  # These next signals are readback values on PVARS which are set by the motion
120
120
  # program.
121
- self.scanstatus = epics_signal_r(int, "BL24I-MO-STEP-14:signal:P2401")
122
- self.counter = epics_signal_r(int, "BL24I-MO-STEP-14:signal:P2402")
121
+ self.scanstatus = epics_signal_r(float, "BL24I-MO-STEP-14:signal:P2401")
122
+ self.counter = epics_signal_r(float, "BL24I-MO-STEP-14:signal:P2402")
123
123
 
124
124
  super().__init__(name)
dodal/devices/motors.py CHANGED
@@ -1,8 +1,3 @@
1
- from dataclasses import dataclass
2
- from typing import List, Tuple
3
-
4
- import numpy as np
5
- from ophyd import EpicsMotor
6
1
  from ophyd_async.core import Device
7
2
  from ophyd_async.epics.motion import Motor
8
3
 
@@ -13,46 +8,3 @@ class XYZPositioner(Device):
13
8
  self.y = Motor(prefix + "Y")
14
9
  self.z = Motor(prefix + "Z")
15
10
  super().__init__(name)
16
-
17
-
18
- @dataclass
19
- class MotorLimitHelper:
20
- """
21
- Represents motor limit(s)
22
- """
23
-
24
- motor: EpicsMotor
25
-
26
- def is_within(self, position: float) -> bool:
27
- """Checks position against limits
28
-
29
- :param position: The position to check
30
- :return: True if position is within the limits
31
- """
32
- low = float(self.motor.low_limit_travel.get())
33
- high = float(self.motor.high_limit_travel.get())
34
- return low <= position <= high
35
-
36
-
37
- @dataclass
38
- class XYZLimitBundle:
39
- """
40
- Holder for limits reflecting an x, y, z bundle
41
- """
42
-
43
- x: MotorLimitHelper
44
- y: MotorLimitHelper
45
- z: MotorLimitHelper
46
-
47
- def position_valid(
48
- self, position: np.ndarray | List[float] | Tuple[float, float, float]
49
- ):
50
- if len(position) != 3:
51
- raise ValueError(
52
- f"Position valid expects a 3-vector, got {position} instead"
53
- )
54
- return (
55
- self.x.is_within(position[0])
56
- & self.y.is_within(position[1])
57
- & self.z.is_within(position[2])
58
- )
dodal/devices/robot.py CHANGED
@@ -60,7 +60,8 @@ class BartRobot(StandardReadable, Movable):
60
60
  self.program_running = epics_signal_r(bool, prefix + "PROGRAM_RUNNING")
61
61
  self.program_name = epics_signal_r(str, prefix + "PROGRAM_NAME")
62
62
  self.error_str = epics_signal_r(str, prefix + "PRG_ERR_MSG")
63
- self.error_code = epics_signal_r(int, prefix + "PRG_ERR_CODE")
63
+ # Change error_code to int type when https://github.com/bluesky/ophyd-async/issues/280 released
64
+ self.error_code = epics_signal_r(float, prefix + "PRG_ERR_CODE")
64
65
  super().__init__(name=name)
65
66
 
66
67
  async def pin_mounted_or_no_pin_found(self):
dodal/devices/smargon.py CHANGED
@@ -1,14 +1,16 @@
1
+ from collections.abc import Generator
2
+ from dataclasses import dataclass
1
3
  from enum import Enum
4
+ from math import isclose
5
+ from typing import Collection, cast
2
6
 
3
- from ophyd import Component as Cpt
4
- from ophyd import Device, EpicsMotor, EpicsSignal
5
- from ophyd.epics_motor import MotorBundle
6
- from ophyd.status import StatusBase
7
+ from bluesky import plan_stubs as bps
8
+ from bluesky.utils import Msg
9
+ from ophyd_async.core import AsyncStatus, Device, StandardReadable, wait_for_value
10
+ from ophyd_async.epics.motion import Motor
11
+ from ophyd_async.epics.signal import epics_signal_r
7
12
 
8
- from dodal.devices.motors import MotorLimitHelper, XYZLimitBundle
9
- from dodal.devices.status import await_approx_value
10
13
  from dodal.devices.util.epics_util import SetWhenEnabled
11
- from dodal.devices.util.motor_utils import ExtendedEpicsMotor
12
14
 
13
15
 
14
16
  class StubPosition(Enum):
@@ -16,6 +18,13 @@ class StubPosition(Enum):
16
18
  RESET_TO_ROBOT_LOAD = 1
17
19
 
18
20
 
21
+ def approx_equal_to(target, deadband: float = 1e-9):
22
+ def approx_equal_to_target(value):
23
+ return isclose(target, value, rel_tol=0, abs_tol=deadband)
24
+
25
+ return approx_equal_to_target
26
+
27
+
19
28
  class StubOffsets(Device):
20
29
  """Stub offsets are used to change the internal co-ordinate system of the smargon by
21
30
  adding an offset to x, y, z.
@@ -25,23 +34,64 @@ class StubOffsets(Device):
25
34
  set them so that the current position is zero or to pre-defined positions.
26
35
  """
27
36
 
28
- parent: "Smargon"
37
+ def __init__(self, name: str = "", prefix: str = ""):
38
+ self.center_at_current_position = SetWhenEnabled(prefix=prefix + "CENTER_CS")
39
+ self.to_robot_load = SetWhenEnabled(prefix=prefix + "SET_STUBS_TO_RL")
40
+ super().__init__(name)
29
41
 
30
- center_at_current_position = Cpt(SetWhenEnabled, "CENTER_CS")
31
- to_robot_load = Cpt(SetWhenEnabled, "SET_STUBS_TO_RL")
32
-
33
- def set(self, pos: StubPosition) -> StatusBase:
42
+ @AsyncStatus.wrap
43
+ async def set(self, pos: StubPosition):
34
44
  if pos == StubPosition.CURRENT_AS_CENTER:
35
- status = self.center_at_current_position.set(1)
36
- status &= await_approx_value(self.parent.x, 0.0, deadband=0.1)
37
- status &= await_approx_value(self.parent.y, 0.0, deadband=0.1)
38
- status &= await_approx_value(self.parent.z, 0.0, deadband=0.1)
39
- return status
45
+ await self.center_at_current_position.set(1)
46
+ smargon = cast(Smargon, self.parent)
47
+ await wait_for_value(
48
+ smargon.x.user_readback, approx_equal_to(0.0, 0.1), None
49
+ )
50
+ await wait_for_value(
51
+ smargon.y.user_readback, approx_equal_to(0.0, 0.1), None
52
+ )
53
+ await wait_for_value(
54
+ smargon.z.user_readback, approx_equal_to(0.0, 0.1), None
55
+ )
40
56
  else:
41
- return self.to_robot_load.set(1)
57
+ await self.to_robot_load.set(1)
58
+
59
+
60
+ @dataclass
61
+ class AxisLimit:
62
+ """Represents the minimum and maximum allowable values on an axis"""
63
+
64
+ min_value: float
65
+ max_value: float
66
+
67
+ def contains(self, pos: float):
68
+ """Determine if the specified value is within limits.
69
+
70
+ Args:
71
+ pos: the value to check
72
+
73
+ Returns:
74
+ True if the value does not exceed the limits
75
+ """
76
+ return self.min_value <= pos <= self.max_value
42
77
 
43
78
 
44
- class Smargon(MotorBundle):
79
+ @dataclass
80
+ class XYZLimits:
81
+ """The limits of the smargon x, y, z axes."""
82
+
83
+ x: AxisLimit
84
+ y: AxisLimit
85
+ z: AxisLimit
86
+
87
+ def position_valid(self, pos: Collection[float]) -> bool:
88
+ return all(
89
+ axis_limits.contains(value)
90
+ for axis_limits, value in zip([self.x, self.y, self.z], pos)
91
+ )
92
+
93
+
94
+ class Smargon(StandardReadable):
45
95
  """
46
96
  Real motors added to allow stops following pin load (e.g. real_x1.stop() )
47
97
  X1 and X2 real motors provide compound chi motion as well as the compound X travel,
@@ -49,35 +99,39 @@ class Smargon(MotorBundle):
49
99
  Robot loading can nudge these and lead to errors.
50
100
  """
51
101
 
52
- x = Cpt(ExtendedEpicsMotor, "X")
53
- y = Cpt(EpicsMotor, "Y")
54
- z = Cpt(EpicsMotor, "Z")
55
- chi = Cpt(EpicsMotor, "CHI")
56
- phi = Cpt(EpicsMotor, "PHI")
57
- omega = Cpt(ExtendedEpicsMotor, "OMEGA")
58
-
59
- real_x1 = Cpt(EpicsMotor, "MOTOR_3")
60
- real_x2 = Cpt(EpicsMotor, "MOTOR_4")
61
- real_y = Cpt(EpicsMotor, "MOTOR_1")
62
- real_z = Cpt(EpicsMotor, "MOTOR_2")
63
- real_phi = Cpt(EpicsMotor, "MOTOR_5")
64
- real_chi = Cpt(EpicsMotor, "MOTOR_6")
65
-
66
- stub_offsets = Cpt(StubOffsets, "")
67
-
68
- disabled = Cpt(EpicsSignal, "DISABLED")
69
-
70
- def get_xyz_limits(self) -> XYZLimitBundle:
71
- """Get the limits for the x, y and z axes.
72
-
73
- Note that these limits may not yet be valid until wait_for_connection is called
74
- on this MotorBundle.
102
+ def __init__(self, prefix: str = "", name: str = ""):
103
+ with self.add_children_as_readables():
104
+ self.x = Motor(prefix + "X")
105
+ self.y = Motor(prefix + "Y")
106
+ self.z = Motor(prefix + "Z")
107
+ self.chi = Motor(prefix + "CHI")
108
+ self.phi = Motor(prefix + "PHI")
109
+ self.omega = Motor(prefix + "OMEGA")
110
+ self.real_x1 = Motor(prefix + "MOTOR_3")
111
+ self.real_x2 = Motor(prefix + "MOTOR_4")
112
+ self.real_y = Motor(prefix + "MOTOR_1")
113
+ self.real_z = Motor(prefix + "MOTOR_2")
114
+ self.real_phi = Motor(prefix + "MOTOR_5")
115
+ self.real_chi = Motor(prefix + "MOTOR_6")
116
+ self.stub_offsets = StubOffsets(prefix=prefix)
117
+ self.disabled = epics_signal_r(int, prefix + "DISABLED")
118
+
119
+ super().__init__(name)
120
+
121
+ def get_xyz_limits(self) -> Generator[Msg, None, XYZLimits]:
122
+ """Obtain a plan stub that returns the smargon XYZ axis limits
123
+
124
+ Yields:
125
+ Bluesky messages
75
126
 
76
127
  Returns:
77
- XYZLimitBundle: The limits for the underlying motors.
128
+ the axis limits
78
129
  """
79
- return XYZLimitBundle(
80
- MotorLimitHelper(self.x),
81
- MotorLimitHelper(self.y),
82
- MotorLimitHelper(self.z),
83
- )
130
+ limits = {}
131
+ for name, pv in [
132
+ (attr_name, getattr(self, attr_name)) for attr_name in ["x", "y", "z"]
133
+ ]:
134
+ min_value = yield from bps.rd(pv.low_limit_travel)
135
+ max_value = yield from bps.rd(pv.high_limit_travel)
136
+ limits[name] = AxisLimit(min_value, max_value)
137
+ return XYZLimits(**limits)
dodal/devices/thawer.py CHANGED
@@ -1,15 +1,48 @@
1
+ from asyncio import Task, create_task, sleep
1
2
  from enum import Enum
2
3
 
3
- from ophyd_async.core import StandardReadable
4
+ from bluesky.protocols import Stoppable
5
+ from ophyd_async.core import AsyncStatus, Device, SignalRW, StandardReadable
4
6
  from ophyd_async.epics.signal import epics_signal_rw
5
7
 
6
8
 
9
+ class ThawingException(Exception):
10
+ pass
11
+
12
+
7
13
  class ThawerStates(str, Enum):
8
14
  OFF = "Off"
9
15
  ON = "On"
10
16
 
11
17
 
12
- class Thawer(StandardReadable):
18
+ class ThawingTimer(Device):
19
+ def __init__(self, control_signal: SignalRW[ThawerStates]) -> None:
20
+ self._control_signal = control_signal
21
+ self._thawing_task: Task | None = None
22
+ super().__init__("thaw_for_time_s")
23
+
24
+ @AsyncStatus.wrap
25
+ async def set(self, time_to_thaw_for: float):
26
+ await self._control_signal.set(ThawerStates.ON)
27
+ if self._thawing_task and not self._thawing_task.done():
28
+ raise ThawingException("Thawing task already in progress")
29
+ self._thawing_task = create_task(sleep(time_to_thaw_for))
30
+ try:
31
+ await self._thawing_task
32
+ finally:
33
+ await self._control_signal.set(ThawerStates.OFF)
34
+
35
+ async def stop(self):
36
+ if self._thawing_task:
37
+ self._thawing_task.cancel()
38
+
39
+
40
+ class Thawer(StandardReadable, Stoppable):
13
41
  def __init__(self, prefix: str, name: str = "") -> None:
14
42
  self.control = epics_signal_rw(ThawerStates, prefix + ":CTRL")
43
+ self.thaw_for_time_s = ThawingTimer(self.control)
15
44
  super().__init__(name)
45
+
46
+ async def stop(self):
47
+ await self.thaw_for_time_s.stop()
48
+ await self.control.set(ThawerStates.OFF)
@@ -1,10 +1,14 @@
1
1
  from functools import partial
2
2
  from typing import Callable
3
3
 
4
- from ophyd import Component, Device, EpicsSignal
4
+ from bluesky.protocols import Movable
5
+ from ophyd import Component, EpicsSignal
6
+ from ophyd import Device as OphydDevice
5
7
  from ophyd.status import Status, StatusBase
8
+ from ophyd_async.core import AsyncStatus, wait_for_value
9
+ from ophyd_async.core import Device as OphydAsyncDevice
10
+ from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw
6
11
 
7
- from dodal.devices.status import await_value
8
12
  from dodal.log import LOGGER
9
13
 
10
14
 
@@ -24,7 +28,7 @@ def epics_signal_put_wait(pv_name: str, wait: float = 3.0) -> Component[EpicsSig
24
28
  def run_functions_without_blocking(
25
29
  functions_to_chain: list[Callable[[], StatusBase]],
26
30
  timeout: float = 60.0,
27
- associated_obj: Device | None = None,
31
+ associated_obj: OphydDevice | None = None,
28
32
  ) -> Status:
29
33
  """Creates and initiates an asynchronous chaining of functions which return a status
30
34
 
@@ -112,16 +116,15 @@ def call_func(func: Callable[[], StatusBase]) -> StatusBase:
112
116
  return func()
113
117
 
114
118
 
115
- class SetWhenEnabled(Device):
119
+ class SetWhenEnabled(OphydAsyncDevice, Movable):
116
120
  """A device that sets the proc field of a PV when it becomes enabled."""
117
121
 
118
- proc = Component(EpicsSignal, ".PROC")
119
- disp = Component(EpicsSignal, ".DISP")
122
+ def __init__(self, name: str = "", prefix: str = ""):
123
+ self.proc = epics_signal_rw(int, prefix + ".PROC")
124
+ self.disp = epics_signal_r(int, prefix + ".DISP")
125
+ super().__init__(name)
120
126
 
121
- def set(self, proc: int) -> Status:
122
- return run_functions_without_blocking(
123
- [
124
- lambda: await_value(self.disp, 0),
125
- lambda: self.proc.set(proc),
126
- ]
127
- )
127
+ @AsyncStatus.wrap
128
+ async def set(self, value: int):
129
+ await wait_for_value(self.disp, 0, None)
130
+ await self.proc.set(value)
dodal/devices/zebra.py CHANGED
@@ -76,6 +76,10 @@ class RotationDirection(str, Enum):
76
76
  POSITIVE = "Positive"
77
77
  NEGATIVE = "Negative"
78
78
 
79
+ @property
80
+ def multiplier(self):
81
+ return 1 if self == RotationDirection.POSITIVE else -1
82
+
79
83
 
80
84
  class ArmDemand(Enum):
81
85
  ARM = 1
dodal/log.py CHANGED
@@ -11,13 +11,19 @@ from typing import Deque, Tuple, TypedDict
11
11
  from bluesky.log import logger as bluesky_logger
12
12
  from graypy import GELFTCPHandler
13
13
  from ophyd.log import logger as ophyd_logger
14
+ from ophyd_async.log import (
15
+ DEFAULT_DATE_FORMAT,
16
+ DEFAULT_FORMAT,
17
+ DEFAULT_LOG_COLORS,
18
+ ColoredFormatterWithDeviceName,
19
+ )
14
20
  from ophyd_async.log import logger as ophyd_async_logger
15
21
 
16
22
  LOGGER = logging.getLogger("Dodal")
17
23
  LOGGER.setLevel(logging.DEBUG)
18
24
 
19
- DEFAULT_FORMATTER = logging.Formatter(
20
- "[%(asctime)s] %(name)s %(module)s %(levelname)s: %(message)s"
25
+ DEFAULT_FORMATTER = ColoredFormatterWithDeviceName(
26
+ fmt=DEFAULT_FORMAT, datefmt=DEFAULT_DATE_FORMAT, log_colors=DEFAULT_LOG_COLORS
21
27
  )
22
28
  ERROR_LOG_BUFFER_LINES = 20000
23
29
  INFO_LOG_DAYS = 30
@@ -247,3 +253,8 @@ def get_graylog_configuration(
247
253
  return "localhost", 5555
248
254
  else:
249
255
  return "graylog-log-target.diamond.ac.uk", graylog_port or DEFAULT_GRAYLOG_PORT
256
+
257
+
258
+ class _NoOpFileHandler:
259
+ def write(*args, **kwargs):
260
+ pass
File without changes
@@ -1,29 +0,0 @@
1
- from ophyd import Component as Cpt
2
- from ophyd import EpicsMotor
3
- from ophyd.epics_motor import MotorBundle
4
-
5
- from dodal.devices.motors import MotorLimitHelper, XYZLimitBundle
6
-
7
-
8
- class Gonio(MotorBundle):
9
- x = Cpt(EpicsMotor, "X")
10
- y = Cpt(EpicsMotor, "Y")
11
- z = Cpt(EpicsMotor, "Z")
12
- kappa = Cpt(EpicsMotor, "KAPPA")
13
- phi = Cpt(EpicsMotor, "PHI")
14
- omega = Cpt(EpicsMotor, "OMEGA")
15
-
16
- def get_xyz_limits(self) -> XYZLimitBundle:
17
- """Get the limits for the x, y and z axes.
18
-
19
- Note that these limits may not yet be valid until wait_for_connection is called
20
- on this MotorBundle.
21
-
22
- Returns:
23
- XYZLimitBundle: The limits for the underlying motors.
24
- """
25
- return XYZLimitBundle(
26
- MotorLimitHelper(self.x),
27
- MotorLimitHelper(self.y),
28
- MotorLimitHelper(self.z),
29
- )