dls-dodal 1.41.0__py3-none-any.whl → 1.43.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.2
2
2
  Name: dls-dodal
3
- Version: 1.41.0
3
+ Version: 1.43.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>
6
6
  License: Apache License
@@ -246,7 +246,7 @@ Requires-Dist: pipdeptree; extra == "dev"
246
246
  Requires-Dist: pre-commit; extra == "dev"
247
247
  Requires-Dist: psutil; extra == "dev"
248
248
  Requires-Dist: pydata-sphinx-theme>=0.12; extra == "dev"
249
- Requires-Dist: pyright==1.1.394; extra == "dev"
249
+ Requires-Dist: pyright; extra == "dev"
250
250
  Requires-Dist: pytest; extra == "dev"
251
251
  Requires-Dist: pytest-asyncio; extra == "dev"
252
252
  Requires-Dist: pytest-cov; extra == "dev"
@@ -1,31 +1,33 @@
1
1
  dodal/__init__.py,sha256=Ksms_WJF8LTkbm38gEpm1jBpGqcQ8NGvmb2ZJlOE1j8,198
2
2
  dodal/__main__.py,sha256=kP2S2RPitnOWpNGokjZ1Yq-1umOtp5sNOZk2B3tBPLM,111
3
- dodal/_version.py,sha256=vzwCPJLAKjDDFO5dFLzh88htG2LcsTvkffw3I3imiBo,513
3
+ dodal/_version.py,sha256=hlNymnCYWZtVYB03GhirN7pxTIz-ic_EgTTGcoOFMh0,513
4
4
  dodal/cli.py,sha256=NieWNUgLUxyck1rHoFAPJjX1xXLzHNdQ-s4wvxYFfps,3757
5
5
  dodal/log.py,sha256=ry8WMq1S4WMIAPqtqGeKuegMRN7Jy3qdVTJlkpKXkL8,9503
6
6
  dodal/utils.py,sha256=nZ_gilv1KErYKnTmknFYaE-JnCC-RxZWQ975cchklFA,19790
7
7
  dodal/beamline_specific_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  dodal/beamline_specific_utils/i03.py,sha256=P6Ls4FoVtcacH0RJM3v6ZwwGx27oMppcBdW0la-ohTY,377
9
9
  dodal/beamlines/README.md,sha256=K9MkL_GomxlsoTB7Mz-_dJA5NNSbmCfMiutchGg3C8o,404
10
- dodal/beamlines/__init__.py,sha256=VJOFt2N6Ge-kvKzpiGee8so2Qs1BLO4k7SYFaF3lG8U,3118
11
- dodal/beamlines/adsim.py,sha256=_LMMjPou7r-T8Jt9ZSj2SGFLuDUju_WNtF5ID6xiZJU,1925
10
+ dodal/beamlines/__init__.py,sha256=dTmVSfeEVUHgxOTQO94lbQQiJHfFcvK2RAwVeTpCqxo,3150
11
+ dodal/beamlines/adsim.py,sha256=uOmFYZIGyput93XHk9R5ydZdxnTrS_wA2zSEm62UCVU,1930
12
+ dodal/beamlines/aithre.py,sha256=1q7zeMYunOBIWCm203NIkCl5tgVl_-jMWc0f5af-W_E,263
12
13
  dodal/beamlines/b01_1.py,sha256=-NYuJv6FhOgJvNYqDtpYb5HucI5_0HS4uDphRXUWzl4,1807
13
14
  dodal/beamlines/i02_1.py,sha256=d2IyqFMgeaSEyZYm7GMSjTKr7_02SakyC_oARx-XwnY,1204
14
15
  dodal/beamlines/i03.py,sha256=q0dMF0W44kCNXiXOUENaUv3nmAfxyOSr5C1zPonCbvk,14856
15
- dodal/beamlines/i04.py,sha256=IwZOnRPL1PC0Oe8m_L4xwXVW2IYJBo0wSbADLWBupyg,12128
16
+ dodal/beamlines/i04.py,sha256=V0fgXfEJnkh0spDXelK6zwjFChN6VaV9_CtrxJLZx2E,12100
16
17
  dodal/beamlines/i10.py,sha256=lkn_xg0pt-vFuWkUGyl62A0xT-Rzs71JztJ1EeQkMi0,11487
17
18
  dodal/beamlines/i13_1.py,sha256=EgnBzsJ55BmsBtq2sDHD6pKnWZsqqAtL0ZM-JP908zE,2467
18
19
  dodal/beamlines/i18.py,sha256=Y5qLniqUkbYHcGGLPdBbiMILQHonPT2oz5M1hKMGqzs,3434
19
20
  dodal/beamlines/i19_1.py,sha256=I5vz64UsVUkDDT7itdpGVFuYq8zkmPECB0rCbCVUkAY,2325
20
21
  dodal/beamlines/i19_2.py,sha256=DihbYZ27rCdF7fvBclyhZ6Rf0YRSs2rc7hmDNtEr6cA,1939
22
+ dodal/beamlines/i19_optics.py,sha256=TAl2ZNVveRGSbdxRjZC_vW8XFrCZlFB648Bkz7g-8oM,1148
21
23
  dodal/beamlines/i20_1.py,sha256=7ZZhPfjjKAhGjdXOI6mu2FPbMbsSjFHJOPa1toZRa0U,1725
22
24
  dodal/beamlines/i22.py,sha256=XCAVBkZxN9cmxfpGoWaCvo77lu8hVIJ_e3BUc_qxdu0,7664
23
25
  dodal/beamlines/i23.py,sha256=Qnu3Rk9gb2BD3YolMqUXiupt0ehxw5rVnfPJXBWFoCU,973
24
26
  dodal/beamlines/i24.py,sha256=9rBQMCWGdKiRnFbcVvmjiBWiC9WJIJCtLh5m6LkHUtU,7096
25
27
  dodal/beamlines/p38.py,sha256=MwxBqYe_rUIj-MCRIFZpHJmR0JtA22bSFKfBpoDI9P0,5777
26
- dodal/beamlines/p45.py,sha256=0mQ_AEURczc7yzfJzR9QK9i-b6rBGIjGZ4xCIdLNfdI,3031
28
+ dodal/beamlines/p45.py,sha256=2snO895TGwf4LbNIvg4BkvAGSfvZcevdpv_82MRpXKo,2129
27
29
  dodal/beamlines/p99.py,sha256=k24QhYpoOHBd0188Fu3wvmpT6dsu8okiIVqVVckdBkw,1063
28
- dodal/beamlines/training_rig.py,sha256=8u1TulTFgvu28sfo6vFY-uo_VBAYKT66ENXTqegw3y0,1940
30
+ dodal/beamlines/training_rig.py,sha256=TzJnKAfL8Nn5nxCyyt9D9-71YnrvmS8oyGavI_N-iv4,1954
29
31
  dodal/common/__init__.py,sha256=ZC4ICKUDB0BDxRaVy8nmqclVmDBne-dPtk6UJsoFq6I,258
30
32
  dodal/common/coordination.py,sha256=OxIjDiO1-9A9KESRPFtzwkvvQlavbgA5RHemlbubBPg,1168
31
33
  dodal/common/crystal_metadata.py,sha256=XGr-X81G9SZvPx5b4nBCH4FOnywyX_zYVy6zwDxIMVM,1926
@@ -33,10 +35,10 @@ dodal/common/maths.py,sha256=K9x7iL3xXLtWYTV-xlFHDNSTIL9a2UP3Ws7wr6Dm2rQ,1803
33
35
  dodal/common/signal_utils.py,sha256=-p4h7xtGPp13t6HTjgFGcs5nN22kVArlkfCPVjpLuRU,1728
34
36
  dodal/common/types.py,sha256=fkL7UOwDbe3v2_VJ5f1W5RxR98Wx-Ra-LxUZWkNDtls,486
35
37
  dodal/common/udc_directory_provider.py,sha256=v5OBaCUwjtQZAsRQUw6LlVL58UvwwDO1l2MKlilXjdk,2403
36
- dodal/common/visit.py,sha256=2UbbCmgOjZWSCxFzE9RYiTJhA_IoVOegma-Jv-PJqps,5787
38
+ dodal/common/visit.py,sha256=SfsjH-pf0KubwH1Kteke_OXJej_AW1as-t-ZnrfOtik,7435
37
39
  dodal/common/beamlines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
40
  dodal/common/beamlines/beamline_parameters.py,sha256=oIPHooqu5vTAwfqZutsKbzwdi9nvFF8568Mz7jrK5rI,3618
39
- dodal/common/beamlines/beamline_utils.py,sha256=-FEBuz4fYBclgifAODKaaBB5GIQFhPHPaktGS5c5lWc,5236
41
+ dodal/common/beamlines/beamline_utils.py,sha256=s_9A0woipsS8wEsn4FdaKppKAKEYKbSfL6XGpfKosLI,4977
40
42
  dodal/common/beamlines/device_helpers.py,sha256=lh7eih7KoFiqxo8PLQIDjbpBbhHuAXSeApt7K3KF9to,1002
41
43
  dodal/devices/CTAB.py,sha256=5_261Ox6NG2cJIzzwnjWz289BG0nZoE0wKOaI5V5jqM,1998
42
44
  dodal/devices/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -49,7 +51,7 @@ dodal/devices/bimorph_mirror.py,sha256=D5PkrOggJRVAnv38lTdy8rErKLu_O2juLEvSqwcot
49
51
  dodal/devices/cryostream.py,sha256=K-ldpredpeDTzNt4qtQMg99nKJNjBYoXBbK0WJGexzw,656
50
52
  dodal/devices/dcm.py,sha256=JbyxLnrS68nnnv39l9XEWgJgXUBqxX6aFo19MZnL36E,2574
51
53
  dodal/devices/diamond_filter.py,sha256=A--RHd7WuH-IBhvCyENcRCTP4K-mm_Kqpa0pojpHZow,1098
52
- dodal/devices/eiger.py,sha256=n9-rCrIdRnmDNdAz6sCKTYLhiglwlDTmKuMkLUdIWZg,15832
54
+ dodal/devices/eiger.py,sha256=RN3klVASvdTT_jer2HJHUCZWZBKoOUQQdTpsKdgTPfo,15836
53
55
  dodal/devices/eiger_odin.py,sha256=ytUH_18YuM1nJDhplS6OTdtADloYvHpO6ppENjVd4jU,7411
54
56
  dodal/devices/fast_grid_scan.py,sha256=SJTolz-LHlkxWA3Fb0lHa90CH8IjyJ1v2vkaaCURGpU,12044
55
57
  dodal/devices/fluorescence_detector_motion.py,sha256=-1qCSvW0PdT0m6BcoLxrtc0OJ5UDIBsEe11EOLr-gFw,501
@@ -60,7 +62,7 @@ dodal/devices/ipin.py,sha256=eq5jlKw7WGQi8VLrAWpaAIsZmfiVf-5Q0td_B22H6A4,473
60
62
  dodal/devices/linkam3.py,sha256=2sf-_heIsDg4qmqae-w9C2Py8pG8bPB3mT0TFPQIzd0,3869
61
63
  dodal/devices/logging_ophyd_device.py,sha256=dUVE-XhWA56WUXez0mrc4sf322CXY3MVLreTycO5j_A,668
62
64
  dodal/devices/motors.py,sha256=K1df9Pn1ThvsW-g7LrfKWOFaiaQXXUAf2BtbRehzUc4,1108
63
- dodal/devices/p45.py,sha256=hNCfWb8xubuHO8uJ5MpHPurp-r1ss7JMgbZQwHVJg-w,1743
65
+ dodal/devices/p45.py,sha256=hoBPpnj3b-njKqmhjAUFb79AyM4qFbWC9tuN6Aa47Rk,1703
64
66
  dodal/devices/pgm.py,sha256=am-AST9iTqma1PkGOKLozqAokZWbJUbM3TNcqXzB-6A,1132
65
67
  dodal/devices/pressure_jump_cell.py,sha256=h5nMNtr2PMG_AKM6nOB7qNTYT70GRuiGBwC-Ol2Yby0,10548
66
68
  dodal/devices/qbpm.py,sha256=FfrWWAHHtYv3fGRT1qljyPpAwoHJYfbooT9CfKg-oXI,465
@@ -80,6 +82,7 @@ dodal/devices/undulator_dcm.py,sha256=olg8FrIKWqGmhJMuzuvJXH-LQTGhKytvXHPso3Br7C
80
82
  dodal/devices/watsonmarlow323_pump.py,sha256=rwU94YE6esgGLYdh-pe8nBo_3tvgp6brrrbPDrqp5_M,1406
81
83
  dodal/devices/webcam.py,sha256=mef075ynDbzZ4pNAjfxR_9tdTTqF_rM7hAOVEEOV-Do,2408
82
84
  dodal/devices/xbpm_feedback.py,sha256=j8MHhhE0feoe6R54zPKqS5EbQ0bEDR-nOpLHzHhnHHQ,1156
85
+ dodal/devices/aithre_lasershaping/goniometer.py,sha256=YEl0DEXW4Dl9b3nsyfwrM7FDiwEZCXK-evGxlyOJr8k,512
83
86
  dodal/devices/areadetector/plugins/CAM.py,sha256=sZzJm5Ez3eWfXZi_EB67wluhZmMQm1UyOc2bJFfzd1U,964
84
87
  dodal/devices/areadetector/plugins/MJPG.py,sha256=QTsxCoWbofNpLMGPoOR2hWoM33KyntuLepbF0YmX0KE,3031
85
88
  dodal/devices/attenuator/attenuator.py,sha256=Vq9Zsyf56S5fePHGeluImTUtxdwEqttBa2YBIdU5tJU,3993
@@ -95,7 +98,7 @@ dodal/devices/detector/__init__.py,sha256=-RdACL3tzc3lLArWOoGNje48UUlv2fElOmGOz9
95
98
  dodal/devices/detector/det_dim_constants.py,sha256=arBWvzMwybatdSiCMAiwB4Bq1dX-wkLi54xPPfTfQhY,2772
96
99
  dodal/devices/detector/det_dist_to_beam_converter.py,sha256=7keoqZYfvgayePVx97lHYpcFRTJnQOfAk_PYP4EZTZQ,1951
97
100
  dodal/devices/detector/det_resolution.py,sha256=aQkKp24LpRGiwzPAQM3wLVa4ANw32HdrKc2kftHfKQA,3253
98
- dodal/devices/detector/detector.py,sha256=syzkl52kGaMINXCXEviFuYPbgNatm5tioVPDmjgro8s,4768
101
+ dodal/devices/detector/detector.py,sha256=sIOGwkixbJTYPdNiwZjDiY7DfYuYzRomKltO2EztDZE,4770
99
102
  dodal/devices/detector/detector_motion.py,sha256=UGDQriDWRluDZOZh1mDX9w_fPjMD-_BGe11kA36Kezs,1616
100
103
  dodal/devices/i03/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
101
104
  dodal/devices/i03/beamstop.py,sha256=D1-QeGNpNlUE1qtFz-XxgzQGZbvM5bdb09f69yDB7U0,2802
@@ -116,6 +119,7 @@ dodal/devices/i18/diode.py,sha256=q8ddVYT7yDXwURzxw5gfXlGT1tFirNfHBmiKnpvvXHk,40
116
119
  dodal/devices/i18/table.py,sha256=f6OtVSqCFIpXyoHX97CPLpaVDVXUNc2EvgSFP3qVFKo,446
117
120
  dodal/devices/i18/thor_labs_stage.py,sha256=I9JSKY-UqTjN-yBxQWL4CycTNNkUj3vknRrXjA6KZR8,364
118
121
  dodal/devices/i19/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
122
+ dodal/devices/i19/hutch_access.py,sha256=K8hYi5gYg46XPw03qFgwoInapYzNGjf4Fgf9Ifi0gAI,308
119
123
  dodal/devices/i19/shutter.py,sha256=RASOmpVaFBPVkCfPR0fddqrGTEWK-VLPIrqJcObHwNE,2211
120
124
  dodal/devices/i20_1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
121
125
  dodal/devices/i22/dcm.py,sha256=SQDh-Sj1OvplHZ9yTWblJwv8PJrUqxseDPupZOWmcLo,4701
@@ -144,7 +148,7 @@ dodal/devices/oav/pin_image_recognition/manual_test.py,sha256=h1Rto6ZDCB3jWhjSy9
144
148
  dodal/devices/oav/pin_image_recognition/utils.py,sha256=L9ypluYqeOFoS7gQuws-vTNc8LqaKl2ZIDNeQ2JaNpg,8592
145
149
  dodal/devices/oav/snapshots/grid_overlay.py,sha256=CdvCdTKMCiwMwxm2lV28KpcIUSXlscZmWxb73_KKmiI,3694
146
150
  dodal/devices/oav/snapshots/snapshot_with_beam_centre.py,sha256=J77RfE3AGTLNdWc6hvsRn2DUdupzuk_FTDGvdP0jqbU,1962
147
- dodal/devices/oav/snapshots/snapshot_with_grid.py,sha256=EBoCtr1NmOKye2yQHqbTBxSg-DsEKFeyBtMFmOeGPRs,2269
151
+ dodal/devices/oav/snapshots/snapshot_with_grid.py,sha256=KAM7KjF0BzhGxx-MXd4Zc16IBbi1BF8S_VT8T84_2OY,2309
148
152
  dodal/devices/p99/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
149
153
  dodal/devices/p99/sample_stage.py,sha256=DvHU556Gp0wFiufZiwY3o2W4xmsCL5uSwNnhd8HPAnc,528
150
154
  dodal/devices/training_rig/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -168,16 +172,19 @@ dodal/devices/zocalo/zocalo_results.py,sha256=cmKlgu-42CAu2X2aIgjxmfdUXypF4RHRNR
168
172
  dodal/parameters/experiment_parameter_base.py,sha256=O7JamfuJ5cYHkPf9tsHJPqn-OMHTAGouigvM1cDFehE,313
169
173
  dodal/plan_stubs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
170
174
  dodal/plan_stubs/check_topup.py,sha256=3gyLHfHNQBCgEWuAg4QE-ONx7y2Do1vVv5HP8ss0Z1I,5371
171
- dodal/plan_stubs/data_session.py,sha256=33wPwbs0mtMnle0H76mH_RNTc5omld7gNSJ9BvRdUnM,1570
175
+ dodal/plan_stubs/data_session.py,sha256=PsRrGceZg7M5LkjQZ8DD2FlSX1fmoyhMPgLDXTEX3m4,1873
172
176
  dodal/plan_stubs/motor_utils.py,sha256=4c93U_WgjfmX12uNiztVW2oKxGVWa_SKQdJYCUNmsGU,4653
173
177
  dodal/plan_stubs/wrapped.py,sha256=kC8HH7bx3-sLYu2oieY_502tAdT2OECF8n-fqoL5Bfc,4266
174
178
  dodal/plans/__init__.py,sha256=nH1jNxw3DzDMg9O8Uda0kqKIalRVEWBrq07OLY6Ey38,93
175
179
  dodal/plans/save_panda.py,sha256=1fumH7Ih8uDIv8ahAtgQ_vUuR3dz0sfUs4n9TEtEbSs,3053
176
180
  dodal/plans/scanspec.py,sha256=Q0AcvTKRT401iGMRDSqK-D523UX5_ofiVMZ_rNXKOx8,2074
181
+ dodal/plans/verify_undulator_gap.py,sha256=mq2fHtc5o5rSgdTM2xhULOImfjwa6x29tPpeoLw4GcU,553
177
182
  dodal/plans/wrapped.py,sha256=BPMw__RcWvk9v5XnhMsi9_k4KsDEbmXogzD2n1ecbUg,2098
178
- dls_dodal-1.41.0.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
179
- dls_dodal-1.41.0.dist-info/METADATA,sha256=0xKqobHJQk4qTg-57eXoXfkBm1Sgh63jVo4ePiQr2OU,16792
180
- dls_dodal-1.41.0.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
181
- dls_dodal-1.41.0.dist-info/entry_points.txt,sha256=bycw_EKUzup_rxfCetOwcauXV4kLln_OPpPT8jEnr-I,94
182
- dls_dodal-1.41.0.dist-info/top_level.txt,sha256=xIozdmZk_wmMV4wugpq9-6eZs0vgADNUKz3j2UAwlhc,6
183
- dls_dodal-1.41.0.dist-info/RECORD,,
183
+ dodal/plans/preprocessors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
184
+ dodal/plans/preprocessors/verify_undulator_gap.py,sha256=cBZEGq8TW1jrXFXB00iClQVXSEaE_jP_rHMY9WTgYyY,1813
185
+ dls_dodal-1.43.0.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
186
+ dls_dodal-1.43.0.dist-info/METADATA,sha256=Vadfd_CXRwb4ymYcIE_1SwbvWWkGxdwCqX86kIkLcLk,16783
187
+ dls_dodal-1.43.0.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
188
+ dls_dodal-1.43.0.dist-info/entry_points.txt,sha256=bycw_EKUzup_rxfCetOwcauXV4kLln_OPpPT8jEnr-I,94
189
+ dls_dodal-1.43.0.dist-info/top_level.txt,sha256=xIozdmZk_wmMV4wugpq9-6eZs0vgADNUKz3j2UAwlhc,6
190
+ dls_dodal-1.43.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (76.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
dodal/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '1.41.0'
21
- __version_tuple__ = version_tuple = (1, 41, 0)
20
+ __version__ = version = '1.43.0'
21
+ __version_tuple__ = version_tuple = (1, 43, 0)
@@ -13,6 +13,7 @@ _BEAMLINE_NAME_OVERRIDES = {
13
13
  "i20-1": "i20_1",
14
14
  "i19-1": "i19_1",
15
15
  "i19-2": "i19_2",
16
+ "i19-optics": "i19_optics",
16
17
  "s03": "i03",
17
18
  "p46": "training_rig",
18
19
  "p47": "training_rig",
dodal/beamlines/adsim.py CHANGED
@@ -40,7 +40,7 @@ https://epics-containers.github.io/main/tutorials/launch_example.html
40
40
  And ensure that the signals are visible:
41
41
 
42
42
  ```sh
43
- export EPICS_CA_ADDR_LIST=127.0.0.1
43
+ export EPICS_CA_ADDR_LIST=127.0.0.1:5094
44
44
  ```
45
45
 
46
46
  How to use the devices in a plan:
@@ -0,0 +1,9 @@
1
+ from dodal.common.beamlines.beamline_utils import device_factory
2
+ from dodal.devices.aithre_lasershaping.goniometer import Goniometer
3
+
4
+ PREFIX = "LA18L"
5
+
6
+
7
+ @device_factory()
8
+ def goniometer() -> Goniometer:
9
+ return Goniometer(f"{PREFIX}-MO-LSR-01:", "goniometer")
dodal/beamlines/i04.py CHANGED
@@ -46,7 +46,7 @@ ZOOM_PARAMS_FILE = (
46
46
  DISPLAY_CONFIG = "/dls_sw/i04/software/gda_versions/var/display.configuration"
47
47
  DAQ_CONFIGURATION_PATH = "/dls_sw/i04/software/daq_configuration"
48
48
 
49
- REDIS_HOST = os.environ.get("VALKEY_PROD_SVC_SERVICE_HOST", "test_redis")
49
+ REDIS_HOST = "i04-valkey-murko.diamond.ac.uk"
50
50
  REDIS_PASSWORD = os.environ.get("VALKEY_PASSWORD", "test_redis_password")
51
51
  MURKO_REDIS_DB = 7
52
52
 
@@ -0,0 +1,34 @@
1
+ from dodal.common.beamlines.beamline_utils import (
2
+ device_factory,
3
+ )
4
+ from dodal.common.beamlines.beamline_utils import (
5
+ set_beamline as set_utils_beamline,
6
+ )
7
+ from dodal.devices.hutch_shutter import HutchShutter
8
+ from dodal.devices.i19.hutch_access import HutchAccessControl
9
+ from dodal.log import set_beamline as set_log_beamline
10
+ from dodal.utils import BeamlinePrefix
11
+
12
+ BL = "i19-optics"
13
+ PREFIX = BeamlinePrefix("i19", "I")
14
+ set_log_beamline(BL)
15
+ set_utils_beamline(BL)
16
+
17
+
18
+ @device_factory()
19
+ def shutter() -> HutchShutter:
20
+ """Get the i19 hutch shutter device, instantiate it if it hasn't already been.
21
+ If this is called when already instantiated, it will return the existing object.
22
+ """
23
+ return HutchShutter(
24
+ f"{PREFIX.beamline_prefix}-PS-SHTR-01:",
25
+ "shutter",
26
+ )
27
+
28
+
29
+ @device_factory()
30
+ def access_control() -> HutchAccessControl:
31
+ """Get a device that checks the active hutch for i19, instantiate it if it hasn't already been.
32
+ If this is called when already instantiated, it will return the existing object.
33
+ """
34
+ return HutchAccessControl(f"{PREFIX.beamline_prefix}-OP-STAT-01:", "access_control")
dodal/beamlines/p45.py CHANGED
@@ -4,7 +4,7 @@ from ophyd_async.epics.adaravis import AravisDetector
4
4
  from ophyd_async.fastcs.panda import HDFPanda
5
5
 
6
6
  from dodal.common.beamlines.beamline_utils import (
7
- device_instantiation,
7
+ device_factory,
8
8
  get_path_provider,
9
9
  set_path_provider,
10
10
  )
@@ -13,11 +13,13 @@ from dodal.common.beamlines.device_helpers import DET_SUFFIX, HDF5_SUFFIX
13
13
  from dodal.common.visit import StaticVisitPathProvider
14
14
  from dodal.devices.p45 import Choppers, TomoStageWithStretchAndSkew
15
15
  from dodal.log import set_beamline as set_log_beamline
16
- from dodal.utils import get_beamline_name, skip_device
16
+ from dodal.utils import BeamlinePrefix, get_beamline_name
17
17
 
18
18
  BL = get_beamline_name("p45")
19
+ PREFIX = BeamlinePrefix(BL)
19
20
  set_log_beamline(BL)
20
21
  set_utils_beamline(BL)
22
+
21
23
  set_path_provider(
22
24
  StaticVisitPathProvider(
23
25
  BL,
@@ -26,92 +28,51 @@ set_path_provider(
26
28
  )
27
29
 
28
30
 
29
- def sample(
30
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
31
- ) -> TomoStageWithStretchAndSkew:
32
- return device_instantiation(
33
- TomoStageWithStretchAndSkew,
34
- "sample_stage",
35
- "-MO-STAGE-01:",
36
- wait_for_connection,
37
- fake_with_ophyd_sim,
38
- )
31
+ @device_factory()
32
+ def sample() -> TomoStageWithStretchAndSkew:
33
+ return TomoStageWithStretchAndSkew(f"{PREFIX.beamline_prefix}-MO-STAGE-01:")
39
34
 
40
35
 
41
- def choppers(
42
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
43
- ) -> Choppers:
44
- return device_instantiation(
45
- Choppers,
46
- "chopper",
47
- "-MO-CHOP-01:",
48
- wait_for_connection,
49
- fake_with_ophyd_sim,
50
- )
36
+ @device_factory()
37
+ def choppers() -> Choppers:
38
+ return Choppers(f"{PREFIX.beamline_prefix}-MO-CHOP-01:")
51
39
 
52
40
 
53
41
  # Disconnected
54
- @skip_device()
55
- def det(
56
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
57
- ) -> AravisDetector:
58
- return device_instantiation(
59
- AravisDetector,
60
- "det",
61
- "-EA-MAP-01:",
62
- wait_for_connection,
63
- fake_with_ophyd_sim,
42
+ @device_factory(skip=True)
43
+ def det() -> AravisDetector:
44
+ return AravisDetector(
45
+ f"{PREFIX.beamline_prefix}-EA-MAP-01:",
46
+ path_provider=get_path_provider(),
64
47
  drv_suffix=DET_SUFFIX,
65
48
  fileio_suffix=HDF5_SUFFIX,
66
- path_provider=get_path_provider(),
67
49
  )
68
50
 
69
51
 
70
52
  # Disconnected
71
- @skip_device()
72
- def diff(
73
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
74
- ) -> AravisDetector:
75
- return device_instantiation(
76
- AravisDetector,
77
- "diff",
78
- "-EA-DIFF-01:",
79
- wait_for_connection,
80
- fake_with_ophyd_sim,
53
+ @device_factory(skip=True)
54
+ def diff() -> AravisDetector:
55
+ return AravisDetector(
56
+ f"{PREFIX.beamline_prefix}-EA-DIFF-01:",
57
+ path_provider=get_path_provider(),
81
58
  drv_suffix=DET_SUFFIX,
82
59
  fileio_suffix=HDF5_SUFFIX,
83
- path_provider=get_path_provider(),
84
60
  )
85
61
 
86
62
 
87
- # Must find which PandA IOC(s) are compatible
88
63
  # Must document what PandAs are physically connected to
89
64
  # See: https://github.com/bluesky/ophyd-async/issues/284
90
- @skip_device()
91
- def panda1(
92
- wait_for_connection: bool = True,
93
- fake_with_ophyd_sim: bool = False,
94
- ) -> HDFPanda:
95
- return device_instantiation(
96
- HDFPanda,
97
- "panda1",
98
- "-MO-PANDA-01:",
99
- wait_for_connection,
100
- fake_with_ophyd_sim,
65
+ @device_factory(skip=True)
66
+ def panda1() -> HDFPanda:
67
+ return HDFPanda(
68
+ f"{PREFIX.beamline_prefix}-MO-PANDA-01:",
101
69
  path_provider=get_path_provider(),
102
70
  )
103
71
 
104
72
 
105
- @skip_device()
106
- def panda2(
107
- wait_for_connection: bool = True,
108
- fake_with_ophyd_sim: bool = False,
109
- ) -> HDFPanda:
110
- return device_instantiation(
111
- HDFPanda,
112
- "panda2",
113
- "-MO-PANDA-02:",
114
- wait_for_connection,
115
- fake_with_ophyd_sim,
73
+ @device_factory(skip=True)
74
+ def panda2() -> HDFPanda:
75
+ return HDFPanda(
76
+ f"{PREFIX.beamline_prefix}-MO-PANDA-02:",
116
77
  path_provider=get_path_provider(),
117
78
  )
@@ -10,7 +10,10 @@ from dodal.common.beamlines.beamline_utils import (
10
10
  )
11
11
  from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
12
12
  from dodal.common.beamlines.device_helpers import DET_SUFFIX, HDF5_SUFFIX
13
- from dodal.common.visit import LocalDirectoryServiceClient, StaticVisitPathProvider
13
+ from dodal.common.visit import (
14
+ LocalDirectoryServiceClient,
15
+ StaticVisitPathProvider,
16
+ )
14
17
  from dodal.devices.training_rig.sample_stage import TrainingRigSampleStage
15
18
  from dodal.log import set_beamline as set_log_beamline
16
19
  from dodal.utils import BeamlinePrefix, get_beamline_name
@@ -31,6 +34,7 @@ PREFIX = BeamlinePrefix(BL)
31
34
  set_log_beamline(BL)
32
35
  set_utils_beamline(BL)
33
36
 
37
+
34
38
  set_path_provider(
35
39
  StaticVisitPathProvider(
36
40
  BL,
@@ -5,11 +5,13 @@ from typing import Annotated, Final, TypeVar, cast
5
5
  from bluesky.run_engine import call_in_bluesky_event_loop
6
6
  from ophyd import Device as OphydV1Device
7
7
  from ophyd.sim import make_fake_device
8
- from ophyd_async.core import DEFAULT_TIMEOUT
8
+ from ophyd_async.core import (
9
+ DEFAULT_TIMEOUT,
10
+ PathProvider,
11
+ )
9
12
  from ophyd_async.core import Device as OphydV2Device
10
13
  from ophyd_async.core import wait_for_connection as v2_device_wait_for_connection
11
14
 
12
- from dodal.common.types import UpdatingPathProvider
13
15
  from dodal.utils import (
14
16
  AnyDevice,
15
17
  BeamlinePrefix,
@@ -22,7 +24,6 @@ DEFAULT_CONNECTION_TIMEOUT: Final[float] = 5.0
22
24
 
23
25
  ACTIVE_DEVICES: dict[str, AnyDevice] = {}
24
26
  BL = ""
25
- PATH_PROVIDER: UpdatingPathProvider | None = None
26
27
 
27
28
 
28
29
  def set_beamline(beamline: str):
@@ -153,15 +154,11 @@ def device_factory(
153
154
  return decorator
154
155
 
155
156
 
156
- def set_path_provider(provider: UpdatingPathProvider):
157
+ def set_path_provider(provider: PathProvider):
157
158
  global PATH_PROVIDER
158
159
 
159
160
  PATH_PROVIDER = provider
160
161
 
161
162
 
162
- def get_path_provider() -> UpdatingPathProvider:
163
- if PATH_PROVIDER is None:
164
- raise ValueError(
165
- "PathProvider has not been set! Ophyd-async StandardDetectors will not be able to write!"
166
- )
163
+ def get_path_provider() -> PathProvider:
167
164
  return PATH_PROVIDER
dodal/common/visit.py CHANGED
@@ -3,7 +3,8 @@ from pathlib import Path
3
3
  from typing import Literal
4
4
 
5
5
  from aiohttp import ClientSession
6
- from ophyd_async.core import FilenameProvider, PathInfo
6
+ from event_model import RunStart
7
+ from ophyd_async.core import FilenameProvider, PathInfo, PathProvider
7
8
  from pydantic import BaseModel
8
9
 
9
10
  from dodal.common.types import UpdatingPathProvider
@@ -78,7 +79,7 @@ class RemoteDirectoryServiceClient(DirectoryServiceClient):
78
79
  ):
79
80
  response.raise_for_status()
80
81
  json = await response.json()
81
- return DataCollectionIdentifier.model_validate_json(json)
82
+ return DataCollectionIdentifier.model_validate(json)
82
83
 
83
84
 
84
85
  class LocalDirectoryServiceClient(DirectoryServiceClient):
@@ -150,3 +151,42 @@ class StaticVisitPathProvider(UpdatingPathProvider):
150
151
  return PathInfo(
151
152
  directory_path=self._root, filename=self._filename_provider(device_name)
152
153
  )
154
+
155
+
156
+ DEFAULT_TEMPLATE = "{device_name}-{instrument}-{scan_id}"
157
+
158
+
159
+ class StartDocumentPathProvider(PathProvider):
160
+ """A PathProvider that sources from metadata in a RunStart document.
161
+
162
+ This uses metadata from a RunStart document to determine file names and data session
163
+ directories. The file naming defaults to "{device_name}-{instrument}-{scan_id}", so
164
+ the file name is incremented by scan number. A template can be included in the
165
+ StartDocument to allow for custom naming conventions.
166
+
167
+ """
168
+
169
+ def __init__(self) -> None:
170
+ self._doc = {}
171
+
172
+ def update_run(self, name: str, start_doc: RunStart) -> None:
173
+ """Cache a start document.
174
+
175
+ This can be plugged into the run engine's subscribe method.
176
+ """
177
+ if name == "start":
178
+ self._doc = start_doc
179
+
180
+ def __call__(self, device_name: str | None = None) -> PathInfo:
181
+ """Returns the directory path and filename for a given data_session.
182
+
183
+ The default template for file naming is: "{device_name}-{instrument}-{scan_id}"
184
+ however, this can be changed by providing a template in the start document. For
185
+ example: "template": "custom-{device_name}--{scan_id}".
186
+
187
+ If you do not provide a data_session_directory it will default to "/tmp".
188
+ """
189
+ template = self._doc.get("template", DEFAULT_TEMPLATE)
190
+ sub_path = template.format_map(self._doc | {"device_name": device_name})
191
+ data_session_directory = Path(self._doc.get("data_session_directory", "/tmp"))
192
+ return PathInfo(directory_path=data_session_directory, filename=sub_path)
@@ -0,0 +1,15 @@
1
+ from ophyd_async.core import StandardReadable
2
+ from ophyd_async.epics.motor import Motor
3
+
4
+
5
+ class Goniometer(StandardReadable):
6
+ """Goniometer and the stages it sits on"""
7
+
8
+ def __init__(self, prefix: str, name: str = "") -> None:
9
+ self.x = Motor(prefix + "X")
10
+ self.y = Motor(prefix + "Y")
11
+ self.z = Motor(prefix + "Z")
12
+ self.sampy = Motor(prefix + "SAMPY")
13
+ self.sampz = Motor(prefix + "SAMPZ")
14
+ self.omega = Motor(prefix + "OMEGA")
15
+ super().__init__(name)
@@ -32,7 +32,7 @@ class DetectorParams(BaseModel):
32
32
  # Must use model_dump(by_alias=True) if serialising!
33
33
 
34
34
  expected_energy_ev: float | None = None
35
- exposure_time: float
35
+ exposure_time_s: float
36
36
  directory: str # : Path https://github.com/DiamondLightSource/dodal/issues/774
37
37
  prefix: str
38
38
  detector_distance: float
dodal/devices/eiger.py CHANGED
@@ -221,11 +221,11 @@ class EigerDetector(Device, Stageable):
221
221
  def set_cam_pvs(self) -> AndStatus:
222
222
  assert self.detector_params is not None
223
223
  status = self.cam.acquire_time.set(
224
- self.detector_params.exposure_time,
224
+ self.detector_params.exposure_time_s,
225
225
  timeout=self.timeouts.general_status_timeout,
226
226
  )
227
227
  status &= self.cam.acquire_period.set(
228
- self.detector_params.exposure_time,
228
+ self.detector_params.exposure_time_s,
229
229
  timeout=self.timeouts.general_status_timeout,
230
230
  )
231
231
  status &= self.cam.num_exposures.set(
@@ -0,0 +1,8 @@
1
+ from ophyd_async.core import StandardReadable
2
+ from ophyd_async.epics.core import epics_signal_r
3
+
4
+
5
+ class HutchAccessControl(StandardReadable):
6
+ def __init__(self, prefix: str, name: str = "") -> None:
7
+ self.active_hutch = epics_signal_r(str, f"{prefix}EHStatus.VALA")
8
+ super().__init__(name)
@@ -34,9 +34,10 @@ class SnapshotWithGrid(MJPG):
34
34
  box_width = await self.box_width.get_value()
35
35
  num_boxes_x = await self.num_boxes_x.get_value()
36
36
  num_boxes_y = await self.num_boxes_y.get_value()
37
-
38
- assert isinstance(filename_str := await self.filename.get_value(), str)
39
- assert isinstance(directory_str := await self.directory.get_value(), str)
37
+ filename_str = await self.filename.get_value()
38
+ assert isinstance(filename_str, str)
39
+ directory_str = await self.directory.get_value()
40
+ assert isinstance(directory_str, str)
40
41
 
41
42
  add_grid_border_overlay_to_image(
42
43
  image, int(top_left_x), int(top_left_y), box_width, num_boxes_x, num_boxes_y
dodal/devices/p45.py CHANGED
@@ -7,13 +7,13 @@ class SampleY(StandardReadable):
7
7
  Motors for controlling the sample's y position and stretch in the y axis.
8
8
  """
9
9
 
10
- def __init__(self, prefix: str, name="") -> None:
10
+ def __init__(self, prefix: str, name: str = ""):
11
11
  with self.add_children_as_readables():
12
12
  self.base = Motor(prefix + "CS:Y")
13
13
  self.stretch = Motor(prefix + "CS:Y:STRETCH")
14
14
  self.top = Motor(prefix + "Y:TOP")
15
15
  self.bottom = Motor(prefix + "Y:BOT")
16
- super().__init__(name=name)
16
+ super().__init__(name)
17
17
 
18
18
 
19
19
  class SampleTheta(StandardReadable):
@@ -21,13 +21,13 @@ class SampleTheta(StandardReadable):
21
21
  Motors for controlling the sample's theta position and skew
22
22
  """
23
23
 
24
- def __init__(self, prefix: str, name="") -> None:
24
+ def __init__(self, prefix: str, name: str = ""):
25
25
  with self.add_children_as_readables():
26
26
  self.base = Motor(prefix + "THETA:POS")
27
27
  self.skew = Motor(prefix + "THETA:SKEW")
28
28
  self.top = Motor(prefix + "THETA:TOP")
29
29
  self.bottom = Motor(prefix + "THETA:BOT")
30
- super().__init__(name=name)
30
+ super().__init__(name)
31
31
 
32
32
 
33
33
  class TomoStageWithStretchAndSkew(StandardReadable):
@@ -35,12 +35,12 @@ class TomoStageWithStretchAndSkew(StandardReadable):
35
35
  Grouping of motors for the P45 tomography stage
36
36
  """
37
37
 
38
- def __init__(self, prefix: str, name="") -> None:
38
+ def __init__(self, prefix: str, name: str = ""):
39
39
  with self.add_children_as_readables():
40
40
  self.x = Motor(prefix + "X")
41
41
  self.y = SampleY(prefix)
42
42
  self.theta = SampleTheta(prefix)
43
- super().__init__(name=name)
43
+ super().__init__(name)
44
44
 
45
45
 
46
46
  class Choppers(StandardReadable):
@@ -48,8 +48,8 @@ class Choppers(StandardReadable):
48
48
  Grouping for the P45 chopper motors
49
49
  """
50
50
 
51
- def __init__(self, prefix: str, name="") -> None:
51
+ def __init__(self, prefix: str, name: str = ""):
52
52
  with self.add_children_as_readables():
53
53
  self.x = Motor(prefix + "ENDAT")
54
54
  self.y = Motor(prefix + "BISS")
55
- super().__init__(name=name)
55
+ super().__init__(name)
@@ -1,6 +1,9 @@
1
+ import logging
2
+
1
3
  from bluesky import plan_stubs as bps
2
4
  from bluesky import preprocessors as bpp
3
5
  from bluesky.utils import MsgGenerator, make_decorator
6
+ from ophyd_async.core import PathProvider
4
7
 
5
8
  from dodal.common.beamlines.beamline_utils import get_path_provider
6
9
  from dodal.common.types import UpdatingPathProvider
@@ -10,7 +13,7 @@ DATA_GROUPS = "data_groups"
10
13
 
11
14
 
12
15
  def attach_data_session_metadata_wrapper(
13
- plan: MsgGenerator, provider: UpdatingPathProvider | None = None
16
+ plan: MsgGenerator, provider: PathProvider | None = None
14
17
  ) -> MsgGenerator:
15
18
  """
16
19
  Attach data session metadata to the runs within a plan and make it correlate
@@ -30,14 +33,19 @@ def attach_data_session_metadata_wrapper(
30
33
  Yields:
31
34
  Iterator[Msg]: Plan messages
32
35
  """
33
- if provider is None:
34
- provider = get_path_provider()
35
- yield from bps.wait_for([provider.update])
36
- ress = yield from bps.wait_for([provider.data_session])
37
- data_session = ress[0].result()
38
- # https://github.com/DiamondLightSource/dodal/issues/452
39
- # As part of 452, write each dataCollection into their own folder, then can use resource_dir directly
40
- yield from bpp.inject_md_wrapper(plan, md={DATA_SESSION: data_session})
36
+ provider = provider or get_path_provider()
37
+ if isinstance(provider, UpdatingPathProvider):
38
+ yield from bps.wait_for([provider.update])
39
+ ress = yield from bps.wait_for([provider.data_session])
40
+ data_session = ress[0].result()
41
+ # https://github.com/DiamondLightSource/dodal/issues/452
42
+ # As part of 452, write each dataCollection into their own folder, then can use resource_dir directly
43
+ yield from bpp.inject_md_wrapper(plan, md={DATA_SESSION: data_session})
44
+ else:
45
+ logging.warning(
46
+ f"{provider} is not an UpdatingPathProvider, {attach_data_session_metadata_wrapper.__name__} will have no effect"
47
+ )
48
+ yield from plan
41
49
 
42
50
 
43
51
  attach_data_session_metadata_decorator = make_decorator(
File without changes
@@ -0,0 +1,49 @@
1
+ from bluesky.preprocessors import plan_mutator
2
+ from bluesky.utils import Msg, MsgGenerator, make_decorator
3
+
4
+ from dodal.plans.verify_undulator_gap import CheckUndulatorDevices, verify_undulator_gap
5
+
6
+
7
+ def verify_undulator_gap_before_run_wrapper(
8
+ plan: MsgGenerator,
9
+ devices: CheckUndulatorDevices,
10
+ run_key_to_wrap: str | None = None,
11
+ ):
12
+ """
13
+ Modifies the wrapped plan so that it checks the undulator gap before the specified run is opened and sets it to the correct value if needed.
14
+
15
+ After a beam dump, the undulator gap may not return correctly, scientists have often requested that this check is done before collections.
16
+
17
+ Args:
18
+ plan: The plan performing the run.
19
+ devices (CheckUndulatorDevices): Any device composite including the DCM and undulator
20
+ run_key_to_wrap: (str | None): The plan to verify the undulator gap is inserted after the 'open_run' message is seen with
21
+ the matching run key. If not specified, instead wrap the first run encountered.
22
+ """
23
+
24
+ # If no run_key specified, make sure we only do check on first run encountered
25
+ _wrapped_run_name: None | str = None
26
+
27
+ def head(msg: Msg):
28
+ yield from verify_undulator_gap(devices)
29
+ yield msg
30
+
31
+ def insert_plans(msg: Msg):
32
+ nonlocal _wrapped_run_name
33
+
34
+ match msg.command:
35
+ case "open_run":
36
+ if (
37
+ not (run_key_to_wrap or _wrapped_run_name)
38
+ or run_key_to_wrap is msg.run
39
+ ):
40
+ _wrapped_run_name = msg.run if msg.run else "unnamed_run"
41
+ return head(msg), None
42
+ return None, None
43
+
44
+ return plan_mutator(plan, insert_plans)
45
+
46
+
47
+ verify_undulator_gap_before_run_decorator = make_decorator(
48
+ verify_undulator_gap_before_run_wrapper
49
+ )
@@ -0,0 +1,19 @@
1
+ from typing import Protocol, runtime_checkable
2
+
3
+ from bluesky import plan_stubs as bps
4
+
5
+ from dodal.devices.dcm import DCM
6
+ from dodal.devices.undulator import Undulator
7
+
8
+
9
+ @runtime_checkable
10
+ class CheckUndulatorDevices(Protocol):
11
+ undulator: Undulator
12
+ dcm: DCM
13
+
14
+
15
+ def verify_undulator_gap(devices: CheckUndulatorDevices):
16
+ """Verify Undulator gap is correct - it may not be after a beam dump"""
17
+
18
+ energy_in_kev = yield from bps.rd(devices.dcm.energy_in_kev.user_readback)
19
+ yield from bps.abs_set(devices.undulator, energy_in_kev, wait=True)