dls-dodal 1.26.0__py3-none-any.whl → 1.27.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.1
2
2
  Name: dls-dodal
3
- Version: 1.26.0
3
+ Version: 1.27.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
@@ -1,6 +1,6 @@
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=ZreJF-rILTr9IBCA-InSDMjl6Bdsh4uJHrXyWFkH87M,413
3
+ dodal/_version.py,sha256=XlqTK6aXcDzaFgsrMUHCuU_TY8zt9cjwfCWklVgy2Hg,413
4
4
  dodal/adsim.py,sha256=OW2dcS7ciD4Yq9WFw4PN_c5Bwccrmu7R-zr-u6ZCbQM,497
5
5
  dodal/cli.py,sha256=z0UBESrNrq6Kq4rttp4uHcwS1fnOnRkKBRDHSriPpGY,2058
6
6
  dodal/log.py,sha256=grK5-c-V6UjMERwDqYPdKbc_BpycrNb8hP0uteLOVCY,8296
@@ -9,21 +9,21 @@ dodal/beamline_specific_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
9
9
  dodal/beamline_specific_utils/i03.py,sha256=Ixe1anFQl-kwRJubmQx28TIW4Zw8qDxpElNNNapWQHI,396
10
10
  dodal/beamlines/README.md,sha256=K9MkL_GomxlsoTB7Mz-_dJA5NNSbmCfMiutchGg3C8o,404
11
11
  dodal/beamlines/__init__.py,sha256=U0dQYFEUloCdQOs24zyfpPTncJXOO4cDcfHSevVOAw4,2890
12
- dodal/beamlines/i03.py,sha256=BsqiPR-z1svEm0psbTtmW3FR69xcC7_pJqLc5Z27mSQ,15935
13
- dodal/beamlines/i04.py,sha256=2sa7qbNnIDK7r7d8-9Y296Fvzl8TzI_z_K2u7EsUQQw,12063
12
+ dodal/beamlines/i03.py,sha256=nOWm9iUYOwqUDAkBXC4UkYmN2GGD1MbTgyCCMO_YXBw,16394
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=XJpey1-XZnCVpjK6-tODRYeBofqV44BL8lK5OSdQgiI,746
16
16
  dodal/beamlines/i22.py,sha256=TlXgAuRYM4JS733tyZyLMYDyC7ajwg85_XzYI5qh46U,7574
17
17
  dodal/beamlines/i23.py,sha256=iEFkrA4sPQsRLGAmeD263jPMX4u2SF1NK4_KYqnVwow,1402
18
- dodal/beamlines/i24.py,sha256=87ugX8yAv1gqpwskl4RDjqZPraySeD0TDJylI6zUytQ,4569
18
+ dodal/beamlines/i24.py,sha256=LIe8tu_ZJsoeQH7tYRZHSvqQ9C9zBGOXO6rb1DPCYjQ,4595
19
19
  dodal/beamlines/p38.py,sha256=0uRL4GVs1sGobr0BZpDDbtStNJcipT6o4FvoA_OWLaE,7120
20
20
  dodal/beamlines/p45.py,sha256=TNIkC-SBfj0ayZtlLLXW9xCSi5CzJkO8XpAMIo8fjao,2957
21
21
  dodal/common/__init__.py,sha256=ZC4ICKUDB0BDxRaVy8nmqclVmDBne-dPtk6UJsoFq6I,258
22
- dodal/common/coordination.py,sha256=psJ-UWNk4w9YUy-1Vc-CpSn9ea_Ugs2pY5wFKA7JBEY,1133
22
+ dodal/common/coordination.py,sha256=OxIjDiO1-9A9KESRPFtzwkvvQlavbgA5RHemlbubBPg,1168
23
23
  dodal/common/maths.py,sha256=JRSBhbMzwlicKp1_Bsfu9gA79JJA_Dgq9EpbExFH65M,1829
24
24
  dodal/common/types.py,sha256=M0gZs9F7--gREF8VYJn-Y1Mt9mIEgp1aLY3oUpUkSno,546
25
25
  dodal/common/udc_directory_provider.py,sha256=zNlt_VgdAlyBtVN7neTHk_0tWBbI4pPUL7q9WQOzXvo,1260
26
- dodal/common/visit.py,sha256=8q-cee1r59tU_ywsxBvUzRMyP3Mn6zRHbE-pQAzBPVM,7545
26
+ dodal/common/visit.py,sha256=MhrFbLptMG0Wvd2nHubBwQ44qAzoP4Bf4_z_-wO8rh0,6063
27
27
  dodal/common/beamlines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
28
  dodal/common/beamlines/beamline_parameters.py,sha256=N22dtDLw3Hlo7EUmGxe4qFGcu7OnldwCz_mU7yK2rd0,3577
29
29
  dodal/common/beamlines/beamline_utils.py,sha256=AgmH9wpnFQ4DHAA7_Yo0COa2piX2ksFxukFt2_600kA,4488
@@ -32,15 +32,15 @@ dodal/devices/CTAB.py,sha256=_MfL_KH4uDPxq_RuHFEZ9HVXOpUnQb5be3csoz9DdAs,1630
32
32
  dodal/devices/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  dodal/devices/adsim.py,sha256=dMU0TKIuiODHYFHQOH4_5UvB8iJtaJEtjqaEDGjcU-w,311
34
34
  dodal/devices/aperture.py,sha256=0MtTzKMDZ5DVAz0DE0kXI0M76VCp0y9vFsrMggEMpxk,586
35
- dodal/devices/aperturescatterguard.py,sha256=2T1VKzE3-mb9hocZtHRwRqC4Eh_M_r8lCmiYwU_mndE,9906
35
+ dodal/devices/aperturescatterguard.py,sha256=2JJsEPJGJHrI0ztv1cSaP7H5T6qdzDfUcN-VEQ39B8o,11012
36
36
  dodal/devices/attenuator.py,sha256=OD7fElTIMHWk7ZopPqEu29lionm7WwgC0-Kvl8vBIb0,2599
37
- dodal/devices/backlight.py,sha256=9BJcAg1f1kGRlb7bNzeltSDlpBXcxjcW90f87ewnj5k,562
37
+ dodal/devices/backlight.py,sha256=vsNGZB4C_mVMafllvJlOTghsfv6UqALMKUMLXu3WZ5k,1115
38
38
  dodal/devices/beamstop.py,sha256=8L3qhlk-3ZBOp10xK1i8qZqYTGOXX1mVF1MgXoN0dfg,215
39
39
  dodal/devices/cryostream.py,sha256=6MU4rXIOL33C-8F3DVfAtv0ZnwiysTtawjkeePd5IrQ,332
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=cbM6Jqcu7Gao0zFH6u8iTuJfojDDYs0AvseAnRs8iMg,12134
43
+ dodal/devices/fast_grid_scan.py,sha256=_jsiUxpMO2rAGDhTFPN8J35e6E0oreLpKYi6W8wQGzs,12798
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
@@ -50,9 +50,8 @@ dodal/devices/logging_ophyd_device.py,sha256=xw4lbyqq5_ehESGterVEfubJsBiJTWvBp5b
50
50
  dodal/devices/lower_gonio_stages.py,sha256=oJ_Xeuqs5E8AWAoZX52jD1qV6pCSjCOjdwnXFKz_xjw,229
51
51
  dodal/devices/motors.py,sha256=aKtMv5q_4b1eFhzyuk2-D6zDsY_6cqAmG59y5LWUz1s,1328
52
52
  dodal/devices/p45.py,sha256=jzBW2fGRhIbGzSRs5Fgupxro6aqE611n1RTcrTTG-yY,1047
53
- dodal/devices/panda_fast_grid_scan.py,sha256=cQmrs3pQ3P_J1e5C1IOdjDZibRthIHjqY98eaH5kLCE,5424
54
53
  dodal/devices/qbpm1.py,sha256=OY7-WbdxMiLGUK8Z57ezwqSXbHxoPP-y3GvBgj9kgMA,220
55
- dodal/devices/robot.py,sha256=ITMKXiA83KVCAVI3ZpN-wWCLj62uDmxLBFO3PUheaRg,2379
54
+ dodal/devices/robot.py,sha256=V5Gk-e1ZczU7y8SqU4_1YXYQJ6knoVFtXLZmIDY9MI4,2530
56
55
  dodal/devices/s4_slit_gaps.py,sha256=j3kgF9WfGFaU9xdUuiAh-QqI5u_vhiAftaDVINt91SM,243
57
56
  dodal/devices/scatterguard.py,sha256=0qnvhoo3RjLsrxVgIoDJpryqunlgMVgaTsoyKRC2g4Y,331
58
57
  dodal/devices/scintillator.py,sha256=4Dej1a6HRom9GRwTDsaTKGfvloP20POUqIeHqsI8-R8,184
@@ -61,6 +60,7 @@ dodal/devices/smargon.py,sha256=ml96h7E1C31qPo8jocAepSouIVXgpIR0vuMF99nZjqM,2964
61
60
  dodal/devices/status.py,sha256=TuUGidZ4Ar-WCRc_sX0wn58DmL6brj1pMr8rNF5Z6VU,1198
62
61
  dodal/devices/synchrotron.py,sha256=E5vcSum-zoD5vIZxa2Xcl0gAkeRqY6a-AfZQICCwLHg,1947
63
62
  dodal/devices/tetramm.py,sha256=dHA15KkJhBHwtmbivDua_aVqFbvK47TVQG7KXmn8jQ8,8041
63
+ dodal/devices/thawer.py,sha256=L5OYSdzGvx6dIkGgcTbITAbFAm0OKEVVqYBb4MPstOg,382
64
64
  dodal/devices/turbo_slit.py,sha256=W3ZRIqDhq4iMhr5GcIiWvl2U1GaPtGanqkL7upQOZTY,1132
65
65
  dodal/devices/undulator.py,sha256=kn84MQpuBHtQj7H7HeBoAYKXu5buGKvTgs3tf2gdEdw,2074
66
66
  dodal/devices/undulator_dcm.py,sha256=TC9fO55r1YIG_88PPbGGtzfjcRJcaoC2ny51JiDOEX4,5199
@@ -113,9 +113,10 @@ dodal/devices/zocalo/zocalo_interaction.py,sha256=B6TBTDwUlluksLTwC4TiEEgfFzWLOm
113
113
  dodal/devices/zocalo/zocalo_results.py,sha256=U4Vk4OF-eL8w0BR-fbw3k4jyRo6G3Ywaf8NMAkjr4Hs,9658
114
114
  dodal/parameters/experiment_parameter_base.py,sha256=O7JamfuJ5cYHkPf9tsHJPqn-OMHTAGouigvM1cDFehE,313
115
115
  dodal/plans/check_topup.py,sha256=VOkHak88_r-pdTsSnwAJnbvlK2_UhKnO5I36pJmWKvQ,2985
116
- dls_dodal-1.26.0.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
117
- dls_dodal-1.26.0.dist-info/METADATA,sha256=0fEtRaibwFzXvFI-JP3xbxw29Sp_H1ocnv23eMgU5xk,16837
118
- dls_dodal-1.26.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
119
- dls_dodal-1.26.0.dist-info/entry_points.txt,sha256=wpzz9FsTiYxI8OBwLKX9V9ResLwThBSmtRMcPwII0FA,46
120
- dls_dodal-1.26.0.dist-info/top_level.txt,sha256=xIozdmZk_wmMV4wugpq9-6eZs0vgADNUKz3j2UAwlhc,6
121
- dls_dodal-1.26.0.dist-info/RECORD,,
116
+ dodal/plans/data_session_metadata.py,sha256=QNx9rb1EfGBHb21eFekIi7KjNhC0PL-SVKBCggDuNeg,1650
117
+ dls_dodal-1.27.0.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
118
+ dls_dodal-1.27.0.dist-info/METADATA,sha256=mkrOjI4F9mwAZOTOLTSPRfNZu5W1wqlqDo3FOOWn6qs,16837
119
+ dls_dodal-1.27.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
120
+ dls_dodal-1.27.0.dist-info/entry_points.txt,sha256=wpzz9FsTiYxI8OBwLKX9V9ResLwThBSmtRMcPwII0FA,46
121
+ dls_dodal-1.27.0.dist-info/top_level.txt,sha256=xIozdmZk_wmMV4wugpq9-6eZs0vgADNUKz3j2UAwlhc,6
122
+ dls_dodal-1.27.0.dist-info/RECORD,,
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.26.0'
16
- __version_tuple__ = version_tuple = (1, 26, 0)
15
+ __version__ = version = '1.27.0'
16
+ __version_tuple__ = version_tuple = (1, 27, 0)
dodal/beamlines/i03.py CHANGED
@@ -14,17 +14,17 @@ from dodal.devices.dcm import DCM
14
14
  from dodal.devices.detector import DetectorParams
15
15
  from dodal.devices.detector.detector_motion import DetectorMotion
16
16
  from dodal.devices.eiger import EigerDetector
17
- from dodal.devices.fast_grid_scan import FastGridScan
17
+ from dodal.devices.fast_grid_scan import PandAFastGridScan, ZebraFastGridScan
18
18
  from dodal.devices.flux import Flux
19
19
  from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, VFMMirrorVoltages
20
20
  from dodal.devices.oav.oav_detector import OAV, OAVConfigParams
21
21
  from dodal.devices.oav.pin_image_recognition import PinTipDetection
22
- from dodal.devices.panda_fast_grid_scan import PandAFastGridScan
23
22
  from dodal.devices.qbpm1 import QBPM1
24
23
  from dodal.devices.robot import BartRobot
25
24
  from dodal.devices.s4_slit_gaps import S4SlitGaps
26
25
  from dodal.devices.smargon import Smargon
27
26
  from dodal.devices.synchrotron import Synchrotron
27
+ from dodal.devices.thawer import Thawer
28
28
  from dodal.devices.undulator import Undulator
29
29
  from dodal.devices.undulator_dcm import UndulatorDCM
30
30
  from dodal.devices.webcam import Webcam
@@ -199,16 +199,16 @@ def eiger(
199
199
  )
200
200
 
201
201
 
202
- def fast_grid_scan(
202
+ def zebra_fast_grid_scan(
203
203
  wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
204
- ) -> FastGridScan:
205
- """Get the i03 fast_grid_scan device, instantiate it if it hasn't already been.
204
+ ) -> ZebraFastGridScan:
205
+ """Get the i03 zebra_fast_grid_scan device, instantiate it if it hasn't already been.
206
206
  If this is called when already instantiated in i03, it will return the existing object.
207
207
  """
208
208
  return device_instantiation(
209
- device_factory=FastGridScan,
210
- name="fast_grid_scan",
211
- prefix="-MO-SGON-01:",
209
+ device_factory=ZebraFastGridScan,
210
+ name="zebra_fast_grid_scan",
211
+ prefix="-MO-SGON-01:FGS:",
212
212
  wait=wait_for_connection,
213
213
  fake=fake_with_ophyd_sim,
214
214
  )
@@ -219,7 +219,7 @@ def panda_fast_grid_scan(
219
219
  ) -> PandAFastGridScan:
220
220
  """Get the i03 panda_fast_grid_scan device, instantiate it if it hasn't already been.
221
221
  If this is called when already instantiated in i03, it will return the existing object.
222
- This is used instead of the fast_grid_scan device when using the PandA.
222
+ This is used instead of the zebra_fast_grid_scan device when using the PandA.
223
223
  """
224
224
  return device_instantiation(
225
225
  device_factory=PandAFastGridScan,
@@ -480,3 +480,18 @@ def webcam(
480
480
  fake_with_ophyd_sim,
481
481
  url="http://i03-webcam1/axis-cgi/jpg/image.cgi",
482
482
  )
483
+
484
+
485
+ def thawer(
486
+ wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
487
+ ) -> Thawer:
488
+ """Get the i03 thawer, instantiate it if it hasn't already been.
489
+ If this is called when already instantiated in i03, it will return the existing object.
490
+ """
491
+ return device_instantiation(
492
+ Thawer,
493
+ "thawer",
494
+ "-EA-THAW-01",
495
+ wait_for_connection,
496
+ fake_with_ophyd_sim,
497
+ )
dodal/beamlines/i04.py CHANGED
@@ -8,15 +8,17 @@ from dodal.devices.dcm import DCM
8
8
  from dodal.devices.detector import DetectorParams
9
9
  from dodal.devices.detector.detector_motion import DetectorMotion
10
10
  from dodal.devices.eiger import EigerDetector
11
- from dodal.devices.fast_grid_scan import FastGridScan
11
+ from dodal.devices.fast_grid_scan import ZebraFastGridScan
12
12
  from dodal.devices.flux import Flux
13
13
  from dodal.devices.i04.transfocator import Transfocator
14
14
  from dodal.devices.ipin import IPin
15
15
  from dodal.devices.motors import XYZPositioner
16
16
  from dodal.devices.oav.oav_detector import OAV, OAVConfigParams
17
+ from dodal.devices.robot import BartRobot
17
18
  from dodal.devices.s4_slit_gaps import S4SlitGaps
18
19
  from dodal.devices.smargon import Smargon
19
20
  from dodal.devices.synchrotron import Synchrotron
21
+ from dodal.devices.thawer import Thawer
20
22
  from dodal.devices.undulator import Undulator
21
23
  from dodal.devices.xbpm_feedback import XBPMFeedbackI04
22
24
  from dodal.devices.zebra import Zebra
@@ -258,15 +260,15 @@ def eiger(
258
260
  )
259
261
 
260
262
 
261
- def fast_grid_scan(
263
+ def zebra_fast_grid_scan(
262
264
  wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
263
- ) -> FastGridScan:
264
- """Get the i04 fast_grid_scan device, instantiate it if it hasn't already been.
265
+ ) -> ZebraFastGridScan:
266
+ """Get the i04 zebra_fast_grid_scan device, instantiate it if it hasn't already been.
265
267
  If this is called when already instantiated in i04, it will return the existing object.
266
268
  """
267
269
  return device_instantiation(
268
- device_factory=FastGridScan,
269
- name="fast_grid_scan",
270
+ device_factory=ZebraFastGridScan,
271
+ name="zebra_fast_grid_scan",
270
272
  prefix="-MO-SGON-01:",
271
273
  wait=wait_for_connection,
272
274
  fake=fake_with_ophyd_sim,
@@ -363,3 +365,33 @@ def detector_motion(
363
365
  wait=wait_for_connection,
364
366
  fake=fake_with_ophyd_sim,
365
367
  )
368
+
369
+
370
+ def thawer(
371
+ wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
372
+ ) -> Thawer:
373
+ """Get the i04 thawer, instantiate it if it hasn't already been.
374
+ If this is called when already instantiated in i04, it will return the existing object.
375
+ """
376
+ return device_instantiation(
377
+ Thawer,
378
+ "thawer",
379
+ "-EA-THAW-01",
380
+ wait_for_connection,
381
+ fake_with_ophyd_sim,
382
+ )
383
+
384
+
385
+ def robot(
386
+ wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
387
+ ) -> BartRobot:
388
+ """Get the i04 robot device, instantiate it if it hasn't already been.
389
+ If this is called when already instantiated in i04, it will return the existing object.
390
+ """
391
+ return device_instantiation(
392
+ BartRobot,
393
+ "robot",
394
+ "-MO-ROBOT-01:",
395
+ wait_for_connection,
396
+ fake_with_ophyd_sim,
397
+ )
dodal/beamlines/i24.py CHANGED
@@ -11,7 +11,9 @@ from dodal.devices.zebra import Zebra
11
11
  from dodal.log import set_beamline as set_log_beamline
12
12
  from dodal.utils import get_beamline_name, skip_device
13
13
 
14
- ZOOM_PARAMS_FILE = "/dls_sw/i24/software/gda/config/xml/jCameraManZoomLevels.xml"
14
+ ZOOM_PARAMS_FILE = (
15
+ "/dls_sw/i24/software/gda_versions/gda_9_34/config/xml/jCameraManZoomLevels.xml"
16
+ )
15
17
  DISPLAY_CONFIG = "/dls_sw/i24/software/gda_versions/var/display.configuration"
16
18
 
17
19
  BL = get_beamline_name("s24")
@@ -1,4 +1,5 @@
1
1
  import uuid
2
+ from typing import Any
2
3
 
3
4
  from dodal.common.types import Group
4
5
 
@@ -16,19 +17,20 @@ def group_uuid(name: str) -> Group:
16
17
  return f"{name}-{str(uuid.uuid4())[:6]}"
17
18
 
18
19
 
19
- def inject(name: str): # type: ignore
20
+ def inject(name: str) -> Any: # type: ignore
20
21
  """
21
- Function to mark a default argument of a plan method as a reference to a device
22
- that is stored in the Blueapi context, as devices are constructed on startup of the
23
- service, and are not available to be used when writing plans.
24
- Bypasses mypy linting, returning x as Any and therefore valid as a default
25
- argument.
26
- e.g. For a 1-dimensional scan, that is usually performed on a consistent Movable
27
- axis with name "stage_x"
22
+ Function to mark a defaulted argument of a plan as a reference to a device stored
23
+ in another context and not available to be referenced directly.
24
+ Bypasses type checking, returning x as Any and therefore valid as a default
25
+ argument, leaving handling to the context from which the plan is called.
26
+ Assumes that device.name is unique.
27
+ e.g. For a 1-dimensional scan, that is usually performed on a Movable with
28
+ name "stage_x"
29
+
28
30
  def scan(x: Movable = inject("stage_x"), start: float = 0.0 ...)
29
31
 
30
32
  Args:
31
- name (str): Name of a device to be fetched from the Blueapi context
33
+ name (str): Name of a Device to be fetched from an external context
32
34
 
33
35
  Returns:
34
36
  Any: name but without typing checking, valid as any default type
dodal/common/visit.py CHANGED
@@ -3,14 +3,10 @@ from pathlib import Path
3
3
  from typing import Optional
4
4
 
5
5
  from aiohttp import ClientSession
6
- from bluesky import plan_stubs as bps
7
- from bluesky import preprocessors as bpp
8
- from bluesky.utils import make_decorator
9
6
  from ophyd_async.core import DirectoryInfo
10
7
  from pydantic import BaseModel
11
8
 
12
- from dodal.common.beamlines import beamline_utils
13
- from dodal.common.types import MsgGenerator, UpdatingDirectoryProvider
9
+ from dodal.common.types import UpdatingDirectoryProvider
14
10
  from dodal.log import LOGGER
15
11
 
16
12
  """
@@ -156,41 +152,3 @@ class StaticVisitDirectoryProvider(UpdatingDirectoryProvider):
156
152
  raise ValueError(
157
153
  "No current collection, update() needs to be called at least once"
158
154
  )
159
-
160
-
161
- DATA_SESSION = "data_session"
162
- DATA_GROUPS = "data_groups"
163
-
164
-
165
- def attach_metadata(
166
- plan: MsgGenerator, provider: UpdatingDirectoryProvider | None
167
- ) -> MsgGenerator:
168
- """
169
- Attach data session metadata to the runs within a plan and make it correlate
170
- with an ophyd-async DirectoryProvider.
171
-
172
- This updates the directory provider (which in turn makes a call to to a service
173
- to figure out which scan number we are using for such a scan), and ensures the
174
- start document contains the correct data session.
175
-
176
- Args:
177
- plan: The plan to preprocess
178
- provider: The directory provider that participating detectors are aware of.
179
-
180
- Returns:
181
- MsgGenerator: A plan
182
-
183
- Yields:
184
- Iterator[Msg]: Plan messages
185
- """
186
- if provider is None:
187
- provider = beamline_utils.get_directory_provider()
188
- yield from bps.wait_for([provider.update])
189
- directory_info: DirectoryInfo = provider()
190
- # https://github.com/DiamondLightSource/dodal/issues/452
191
- # As part of 452, write each dataCollection into their own folder, then can use resource_dir directly
192
- data_session = directory_info.prefix.removesuffix("-")
193
- yield from bpp.inject_md_wrapper(plan, md={DATA_SESSION: data_session})
194
-
195
-
196
- attach_metadata_decorator = make_decorator(attach_metadata)
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
2
  from collections import OrderedDict, namedtuple
3
3
  from dataclasses import asdict, dataclass
4
+ from enum import Enum
4
5
 
5
6
  from bluesky.protocols import Movable, Reading
6
7
  from ophyd_async.core import AsyncStatus, SignalR, StandardReadable
@@ -48,8 +49,22 @@ class SingleAperturePosition:
48
49
  )
49
50
 
50
51
 
52
+ # Use StrEnum once we stop python 3.10 support
53
+ class AperturePositionGDANames(str, Enum):
54
+ LARGE_APERTURE = "LARGE_APERTURE"
55
+ MEDIUM_APERTURE = "MEDIUM_APERTURE"
56
+ SMALL_APERTURE = "SMALL_APERTURE"
57
+ ROBOT_LOAD = "ROBOT_LOAD"
58
+
59
+ def __str__(self):
60
+ return str(self.value)
61
+
62
+
51
63
  def position_from_params(
52
- name: str, GDA_name: str, radius_microns: float | None, params: dict
64
+ name: str,
65
+ GDA_name: AperturePositionGDANames,
66
+ radius_microns: float | None,
67
+ params: dict,
53
68
  ) -> SingleAperturePosition:
54
69
  return SingleAperturePosition(
55
70
  name,
@@ -77,7 +92,8 @@ def tolerances_from_params(params: dict) -> ApertureScatterguardTolerances:
77
92
 
78
93
  @dataclass
79
94
  class AperturePositions:
80
- """Holds the motor positions needed to select a particular aperture size."""
95
+ """Holds the motor positions needed to select a particular aperture size. This class should be instantiated with definitions for its sizes
96
+ using from_gda_beamline_params"""
81
97
 
82
98
  LARGE: SingleAperturePosition
83
99
  MEDIUM: SingleAperturePosition
@@ -93,13 +109,32 @@ class AperturePositions:
93
109
  @classmethod
94
110
  def from_gda_beamline_params(cls, params):
95
111
  return cls(
96
- LARGE=position_from_params("Large", "LARGE_APERTURE", 100, params),
97
- MEDIUM=position_from_params("Medium", "MEDIUM_APERTURE", 50, params),
98
- SMALL=position_from_params("Small", "SMALL_APERTURE", 20, params),
99
- ROBOT_LOAD=position_from_params("Robot load", "ROBOT_LOAD", None, params),
112
+ LARGE=position_from_params(
113
+ "Large", AperturePositionGDANames.LARGE_APERTURE, 100, params
114
+ ),
115
+ MEDIUM=position_from_params(
116
+ "Medium", AperturePositionGDANames.MEDIUM_APERTURE, 50, params
117
+ ),
118
+ SMALL=position_from_params(
119
+ "Small", AperturePositionGDANames.SMALL_APERTURE, 20, params
120
+ ),
121
+ ROBOT_LOAD=position_from_params(
122
+ "Robot load", AperturePositionGDANames.ROBOT_LOAD, None, params
123
+ ),
100
124
  tolerances=tolerances_from_params(params),
101
125
  )
102
126
 
127
+ def get_position_from_gda_aperture_name(
128
+ self, gda_aperture_name: AperturePositionGDANames
129
+ ) -> SingleAperturePosition:
130
+ apertures = [ap for ap in self.as_list() if ap.GDA_name == gda_aperture_name]
131
+ if not apertures:
132
+ raise ValueError(
133
+ f"Tried to convert unknown aperture name {gda_aperture_name} to a SingleAperturePosition"
134
+ )
135
+ else:
136
+ return apertures[0]
137
+
103
138
  def as_list(self) -> list[SingleAperturePosition]:
104
139
  return [
105
140
  self.LARGE,
@@ -1,20 +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
5
+
6
+
7
+ class BacklightPower(str, Enum):
8
+ ON = "On"
9
+ OFF = "Off"
3
10
 
4
- class Backlight(Device):
5
- """Simple device to trigger the pneumatic in/out."""
6
11
 
7
- OUT = 0
8
- IN = 1
12
+ class BacklightPosition(str, Enum):
13
+ IN = "In"
14
+ OUT = "Out"
15
+
16
+
17
+ class Backlight(StandardReadable):
18
+ """Simple device to trigger the pneumatic in/out."""
9
19
 
10
- pos = Component(EpicsSignal, "-EA-BL-01:CTRL")
11
- # Toggle to switch it On or Off
12
- toggle = Component(EpicsSignal, "-EA-BLIT-01:TOGGLE")
20
+ def __init__(self, prefix: str, name: str = "") -> None:
21
+ with self.add_children_as_readables():
22
+ self.power = epics_signal_rw(BacklightPower, prefix + "-EA-BLIT-01:TOGGLE")
23
+ self.position = epics_signal_rw(
24
+ BacklightPosition, prefix + "-EA-BL-01:CTRL"
25
+ )
26
+ super().__init__(name)
13
27
 
14
- def set(self, position: int) -> StatusBase:
15
- status = self.pos.set(position)
16
- if position == self.OUT:
17
- status &= self.toggle.set("Off")
28
+ @AsyncStatus.wrap
29
+ async def set(self, position: BacklightPosition):
30
+ """This setter will turn the backlight on when we move it in to the beam and off
31
+ when we move it out."""
32
+ await self.position.set(position)
33
+ if position == BacklightPosition.OUT:
34
+ await self.power.set(BacklightPower.OFF)
18
35
  else:
19
- status &= self.toggle.set("On")
20
- return status
36
+ await self.power.set(BacklightPower.ON)
@@ -1,24 +1,27 @@
1
- import threading
2
- import time
3
- from typing import Any
1
+ from abc import ABC
2
+ from typing import Any, Generic, TypeVar
4
3
 
5
4
  import numpy as np
6
5
  from bluesky.plan_stubs import mv
7
6
  from numpy import ndarray
8
- from ophyd import (
9
- Component,
7
+ from ophyd_async.core import (
8
+ AsyncStatus,
10
9
  Device,
11
- EpicsSignal,
12
- EpicsSignalRO,
13
- EpicsSignalWithRBV,
14
10
  Signal,
11
+ SignalR,
12
+ SoftSignalBackend,
13
+ StandardReadable,
14
+ wait_for_value,
15
15
  )
16
- from ophyd.status import DeviceStatus, StatusBase
16
+ from ophyd_async.epics.signal import (
17
+ epics_signal_r,
18
+ epics_signal_rw,
19
+ )
20
+ from ophyd_async.epics.signal.signal import epics_signal_rw_rbv
17
21
  from pydantic import validator
18
22
  from pydantic.dataclasses import dataclass
19
23
 
20
24
  from dodal.devices.motors import XYZLimitBundle
21
- from dodal.devices.status import await_value
22
25
  from dodal.log import LOGGER
23
26
  from dodal.parameters.experiment_parameter_base import AbstractExperimentWithBeamParams
24
27
 
@@ -50,9 +53,6 @@ class GridScanParamsCommon(AbstractExperimentWithBeamParams):
50
53
  layout to EPICS. The parameters and functions of this class are common
51
54
  to both the zebra and panda triggered fast grid scans.
52
55
 
53
- Motion program will do a grid in x-y then rotate omega +90 and perform
54
- a grid in x-z.
55
-
56
56
  The grid specified is where data is taken e.g. it can be assumed the first frame is
57
57
  at x_start, y1_start, z1_start and subsequent frames are N*step_size away.
58
58
  """
@@ -75,6 +75,21 @@ class GridScanParamsCommon(AbstractExperimentWithBeamParams):
75
75
  # Whether to set the stub offsets after centering
76
76
  set_stub_offsets: bool = False
77
77
 
78
+ def get_param_positions(self) -> dict:
79
+ return {
80
+ "x_steps": self.x_steps,
81
+ "y_steps": self.y_steps,
82
+ "z_steps": self.z_steps,
83
+ "x_step_size": self.x_step_size,
84
+ "y_step_size": self.y_step_size,
85
+ "z_step_size": self.z_step_size,
86
+ "x_start": self.x_start,
87
+ "y1_start": self.y1_start,
88
+ "y2_start": self.y2_start,
89
+ "z1_start": self.z1_start,
90
+ "z2_start": self.z2_start,
91
+ }
92
+
78
93
  class Config:
79
94
  arbitrary_types_allowed = True
80
95
  fields = {
@@ -153,21 +168,21 @@ class GridScanParamsCommon(AbstractExperimentWithBeamParams):
153
168
  )
154
169
 
155
170
 
156
- class GridScanParams(GridScanParamsCommon):
157
- """
158
- Holder class for the parameters of a grid scan in a similar
159
- layout to EPICS. These params are used for the zebra-triggered
160
- fast grid scan
171
+ ParamType = TypeVar("ParamType", bound=GridScanParamsCommon)
161
172
 
162
- Motion program will do a grid in x-y then rotate omega +90 and perform
163
- a grid in x-z.
164
173
 
165
- The grid specified is where data is taken e.g. it can be assumed the first frame is
166
- at x_start, y1_start, z1_start and subsequent frames are N*step_size away.
174
+ class ZebraGridScanParams(GridScanParamsCommon):
175
+ """
176
+ Params for standard Zebra FGS. Adds on the dwell time
167
177
  """
168
178
 
169
179
  dwell_time_ms: float = 10
170
180
 
181
+ def get_param_positions(self):
182
+ param_positions = super().get_param_positions()
183
+ param_positions["dwell_time_ms"] = self.dwell_time_ms
184
+ return param_positions
185
+
171
186
  @validator("dwell_time_ms", always=True, check_fields=True)
172
187
  def non_integer_dwell_time(cls, dwell_time_ms: float) -> float:
173
188
  dwell_time_floor_rounded = np.floor(dwell_time_ms)
@@ -181,181 +196,159 @@ class GridScanParams(GridScanParamsCommon):
181
196
  return dwell_time_ms
182
197
 
183
198
 
184
- class GridScanCompleteStatus(DeviceStatus):
199
+ class PandAGridScanParams(GridScanParamsCommon):
200
+ """
201
+ Params for panda constant-motion scan. Adds on the goniometer run-up distance
202
+ """
203
+
204
+ run_up_distance_mm: float = 0.17
205
+
206
+ def get_param_positions(self):
207
+ param_positions = super().get_param_positions()
208
+ param_positions["run_up_distance_mm"] = self.run_up_distance_mm
209
+ return param_positions
210
+
211
+
212
+ class MotionProgram(Device):
213
+ def __init__(self, prefix: str, name: str = "") -> None:
214
+ super().__init__(name)
215
+ self.running = epics_signal_r(float, prefix + "PROGBITS")
216
+ self.program_number = epics_signal_r(float, prefix + "CS1:PROG_NUM")
217
+
218
+
219
+ class ExpectedImages(SignalR[int]):
220
+ def __init__(self, parent: "FastGridScanCommon") -> None:
221
+ super().__init__(SoftSignalBackend(int))
222
+ self.parent: "FastGridScanCommon" = parent
223
+
224
+ async def get_value(self):
225
+ x = await self.parent.x_steps.get_value()
226
+ y = await self.parent.y_steps.get_value()
227
+ z = await self.parent.z_steps.get_value()
228
+ first_grid = x * y
229
+ second_grid = x * z
230
+ return first_grid + second_grid
231
+
232
+
233
+ class FastGridScanCommon(StandardReadable, ABC, Generic[ParamType]):
234
+ """Device for a general fast grid scan
235
+
236
+ When the motion program is started, the goniometer will move in a snake-like grid trajectory,
237
+ with X as the fast axis and Y as the slow axis. If Z steps isn't 0, the goniometer will
238
+ then rotate in the omega direction such that it moves from the X-Y, to the X-Z plane then
239
+ do a second grid scan. The detector is triggered after every x step.
240
+ See https://github.com/DiamondLightSource/hyperion/wiki/Coordinate-Systems for more
185
241
  """
186
- A Status for the grid scan completion
187
- A special status object that notifies watchers (progress bars)
188
- based on comparing device.expected_images to device.position_counter.
242
+
243
+ def __init__(self, prefix: str, name: str = "") -> None:
244
+ super().__init__(name)
245
+ self.x_steps = epics_signal_rw_rbv(int, "X_NUM_STEPS")
246
+ self.y_steps = epics_signal_rw_rbv(
247
+ int, "X_NUM_STEPS"
248
+ ) # Number of vertical steps during the first grid scan
249
+ self.z_steps = epics_signal_rw_rbv(
250
+ int, "X_NUM_STEPS"
251
+ ) # Number of vertical steps during the second grid scan, after the rotation in omega
252
+ self.x_step_size = epics_signal_rw_rbv(float, "X_STEP_SIZE")
253
+ self.y_step_size = epics_signal_rw_rbv(float, "Y_STEP_SIZE")
254
+ self.z_step_size = epics_signal_rw_rbv(float, "Z_STEP_SIZE")
255
+ self.x_start = epics_signal_rw_rbv(float, "X_START")
256
+ self.y1_start = epics_signal_rw_rbv(float, "Y_START")
257
+ self.y2_start = epics_signal_rw_rbv(float, "Y2_START")
258
+ self.z1_start = epics_signal_rw_rbv(float, "Z_START")
259
+ self.z2_start = epics_signal_rw_rbv(float, "Z2_START")
260
+
261
+ self.position_counter = epics_signal_rw(
262
+ int, "POS_COUNTER", write_pv="POS_COUNTER_WRITE"
263
+ )
264
+ self.x_counter = epics_signal_r(int, "X_COUNTER")
265
+ self.y_counter = epics_signal_r(int, "Y_COUNTER")
266
+ self.scan_invalid = epics_signal_r(float, "SCAN_INVALID")
267
+
268
+ self.run_cmd = epics_signal_rw(int, "RUN.PROC")
269
+ self.stop_cmd = epics_signal_rw(int, "STOP.PROC")
270
+ self.status = epics_signal_r(float, "SCAN_STATUS")
271
+
272
+ self.expected_images = ExpectedImages(parent=self)
273
+
274
+ self.motion_program = MotionProgram(prefix)
275
+
276
+ # Kickoff timeout in seconds
277
+ self.KICKOFF_TIMEOUT: float = 5.0
278
+
279
+ self.COMPLETE_STATUS: float = 60.0
280
+
281
+ self.movable_params: dict[str, Signal] = {
282
+ "x_steps": self.x_steps,
283
+ "y_steps": self.y_steps,
284
+ "z_steps": self.z_steps,
285
+ "x_step_size": self.x_step_size,
286
+ "y_step_size": self.y_step_size,
287
+ "z_step_size": self.z_step_size,
288
+ "x_start": self.x_start,
289
+ "y1_start": self.y1_start,
290
+ "y2_start": self.y2_start,
291
+ "z1_start": self.z1_start,
292
+ "z2_start": self.z2_start,
293
+ }
294
+
295
+ @AsyncStatus.wrap
296
+ async def kickoff(self):
297
+ curr_prog = await self.motion_program.program_number.get_value()
298
+ running = await self.motion_program.running.get_value()
299
+ if running:
300
+ LOGGER.info(f"Motion program {curr_prog} still running, waiting...")
301
+ await wait_for_value(self.motion_program.running, 0, self.KICKOFF_TIMEOUT)
302
+
303
+ LOGGER.debug("Running scan")
304
+ await self.run_cmd.set(1)
305
+ LOGGER.info("Waiting for FGS to start")
306
+ await wait_for_value(self.status, 1, self.KICKOFF_TIMEOUT)
307
+ LOGGER.debug("FGS kicked off")
308
+
309
+ @AsyncStatus.wrap
310
+ async def complete(self):
311
+ await wait_for_value(self.status, 0, self.COMPLETE_STATUS)
312
+
313
+
314
+ class ZebraFastGridScan(FastGridScanCommon[ZebraGridScanParams]):
315
+ """Device for standard Zebra FGS. In this scan, the goniometer's velocity profile follows a parabolic shape between X steps,
316
+ with the slowest points occuring at each X step.
189
317
  """
190
318
 
191
- def __init__(self, *args, **kwargs):
192
- super().__init__(*args, **kwargs)
193
- self.start_ts = time.time()
194
-
195
- self.device.position_counter.subscribe(self._notify_watchers)
196
- self.device.status.subscribe(self._running_changed)
197
-
198
- self._name = self.device.name
199
- self._target_count = self.device.expected_images.get()
200
-
201
- def _notify_watchers(self, value, *args, **kwargs):
202
- if not self._watchers:
203
- return
204
- time_elapsed = time.time() - self.start_ts
205
- try:
206
- fraction = 1 - value / self._target_count
207
- except ZeroDivisionError:
208
- fraction = 0
209
- time_remaining = 0
210
- except Exception as e:
211
- fraction = None
212
- time_remaining = None
213
- self.set_exception(e)
214
- self.clean_up()
215
- else:
216
- time_remaining = time_elapsed / fraction
217
- for watcher in self._watchers:
218
- watcher(
219
- name=self._name,
220
- current=value,
221
- initial=0,
222
- target=self._target_count,
223
- unit="images",
224
- precision=0,
225
- fraction=fraction,
226
- time_elapsed=time_elapsed,
227
- time_remaining=time_remaining,
319
+ def __init__(self, prefix: str, name: str = "") -> None:
320
+ super().__init__(prefix, name)
321
+
322
+ # Time taken to travel between X steps
323
+ self.dwell_time_ms = epics_signal_rw_rbv(float, "DWELL_TIME")
324
+ self.movable_params["dwell_time_ms"] = self.dwell_time_ms
325
+
326
+
327
+ class PandAFastGridScan(FastGridScanCommon[PandAGridScanParams]):
328
+ """Device for panda constant-motion scan"""
329
+
330
+ def __init__(self, prefix: str, name: str = "") -> None:
331
+ super().__init__(prefix, name)
332
+ self.time_between_x_steps_ms = (
333
+ epics_signal_rw_rbv( # Used by motion controller to set goniometer velocity
334
+ float, "TIME_BETWEEN_X_STEPS"
228
335
  )
336
+ )
229
337
 
230
- def _running_changed(self, value=None, old_value=None, **kwargs):
231
- if (old_value == 1) and (value == 0):
232
- self.set_finished()
233
- self.clean_up()
338
+ # Distance before and after the grid given to allow goniometer to reach desired speed while it is within the
339
+ # grid
340
+ self.run_up_distance_mm = epics_signal_rw_rbv(float, "RUNUP_DISTANCE")
341
+ self.movable_params["run_up_distance_mm"] = self.run_up_distance_mm
234
342
 
235
- def clean_up(self):
236
- self.device.position_counter.clear_sub(self._notify_watchers)
237
- self.device.status.clear_sub(self._running_changed)
238
343
 
344
+ def set_fast_grid_scan_params(scan: FastGridScanCommon[ParamType], params: ParamType):
345
+ to_move = []
239
346
 
240
- class MotionProgram(Device):
241
- running = Component(EpicsSignalRO, "PROGBITS")
242
- program_number = Component(EpicsSignalRO, "CS1:PROG_NUM")
243
-
244
-
245
- class FastGridScan(Device):
246
- x_steps = Component(EpicsSignalWithRBV, "FGS:X_NUM_STEPS")
247
- y_steps = Component(EpicsSignalWithRBV, "FGS:Y_NUM_STEPS")
248
- z_steps = Component(EpicsSignalWithRBV, "FGS:Z_NUM_STEPS")
249
-
250
- x_step_size = Component(EpicsSignalWithRBV, "FGS:X_STEP_SIZE")
251
- y_step_size = Component(EpicsSignalWithRBV, "FGS:Y_STEP_SIZE")
252
- z_step_size = Component(EpicsSignalWithRBV, "FGS:Z_STEP_SIZE")
253
-
254
- dwell_time_ms = Component(EpicsSignalWithRBV, "FGS:DWELL_TIME")
255
-
256
- x_start = Component(EpicsSignalWithRBV, "FGS:X_START")
257
- y1_start = Component(EpicsSignalWithRBV, "FGS:Y_START")
258
- y2_start = Component(EpicsSignalWithRBV, "FGS:Y2_START")
259
- z1_start = Component(EpicsSignalWithRBV, "FGS:Z_START")
260
- z2_start = Component(EpicsSignalWithRBV, "FGS:Z2_START")
261
-
262
- position_counter = Component(
263
- EpicsSignal, "FGS:POS_COUNTER", write_pv="FGS:POS_COUNTER_WRITE"
264
- )
265
- x_counter = Component(EpicsSignalRO, "FGS:X_COUNTER")
266
- y_counter = Component(EpicsSignalRO, "FGS:Y_COUNTER")
267
- scan_invalid = Component(EpicsSignalRO, "FGS:SCAN_INVALID")
268
-
269
- run_cmd = Component(EpicsSignal, "FGS:RUN.PROC")
270
- stop_cmd = Component(EpicsSignal, "FGS:STOP.PROC")
271
- status = Component(EpicsSignalRO, "FGS:SCAN_STATUS")
272
-
273
- expected_images = Component(Signal)
274
-
275
- motion_program = Component(MotionProgram, "")
276
-
277
- # Kickoff timeout in seconds
278
- KICKOFF_TIMEOUT: float = 5.0
279
-
280
- def __init__(self, *args, **kwargs):
281
- super().__init__(*args, **kwargs)
282
-
283
- def set_expected_images(*_, **__):
284
- x = int(self.x_steps.get())
285
- y = int(self.y_steps.get())
286
- z = int(self.z_steps.get())
287
- first_grid = x * y
288
- second_grid = x * z
289
- self.expected_images.put(first_grid + second_grid)
290
-
291
- self.x_steps.subscribe(set_expected_images)
292
- self.y_steps.subscribe(set_expected_images)
293
- self.z_steps.subscribe(set_expected_images)
294
-
295
- def is_invalid(self) -> bool:
296
- if "GONP" in self.scan_invalid.pvname:
297
- return False
298
- return bool(self.scan_invalid.get())
299
-
300
- def kickoff(self) -> StatusBase:
301
- st = DeviceStatus(device=self, timeout=self.KICKOFF_TIMEOUT)
302
-
303
- def scan():
304
- try:
305
- curr_prog = self.motion_program.program_number.get()
306
- running = self.motion_program.running.get()
307
- if running:
308
- LOGGER.info(f"Motion program {curr_prog} still running, waiting...")
309
- await_value(self.motion_program.running, 0).wait()
310
- LOGGER.debug("Running scan")
311
- self.run_cmd.put(1)
312
- LOGGER.info("Waiting for FGS to start")
313
- await_value(self.status, 1).wait()
314
- st.set_finished()
315
- LOGGER.debug(f"{st} finished, exiting FGS kickoff thread")
316
- except Exception as e:
317
- st.set_exception(e)
318
-
319
- threading.Thread(target=scan, daemon=True).start()
320
- LOGGER.info("Returning FGS kickoff status")
321
- return st
322
-
323
- def complete(self) -> DeviceStatus:
324
- return GridScanCompleteStatus(self)
325
-
326
- def collect(self):
327
- return {}
328
-
329
- def describe_collect(self):
330
- return {}
331
-
332
-
333
- def set_fast_grid_scan_params(scan: FastGridScan, params: GridScanParams):
334
- yield from mv(
335
- scan.x_steps,
336
- params.x_steps,
337
- scan.y_steps,
338
- params.y_steps,
339
- scan.z_steps,
340
- params.z_steps,
341
- scan.x_step_size,
342
- params.x_step_size,
343
- scan.y_step_size,
344
- params.y_step_size,
345
- scan.z_step_size,
346
- params.z_step_size,
347
- scan.dwell_time_ms,
348
- params.dwell_time_ms,
349
- scan.x_start,
350
- params.x_start,
351
- scan.y1_start,
352
- params.y1_start,
353
- scan.y2_start,
354
- params.y2_start,
355
- scan.z1_start,
356
- params.z1_start,
357
- scan.z2_start,
358
- params.z2_start,
359
- scan.position_counter,
360
- 0,
361
- )
347
+ # Create arguments for bps.mv
348
+ for key in scan.movable_params.keys():
349
+ to_move.extend([scan.movable_params[key], params.__dict__[key]])
350
+
351
+ # Counter should always start at 0
352
+ to_move.extend([scan.position_counter, 0])
353
+
354
+ yield from mv(*to_move)
dodal/devices/robot.py CHANGED
@@ -35,6 +35,8 @@ class BartRobot(StandardReadable, Movable):
35
35
  self.gonio_pin_sensor = epics_signal_r(PinMounted, prefix + "PIN_MOUNTED")
36
36
  self.next_pin = epics_signal_rw_rbv(float, prefix + "NEXT_PIN")
37
37
  self.next_puck = epics_signal_rw_rbv(float, prefix + "NEXT_PUCK")
38
+ self.next_sample_id = epics_signal_rw_rbv(float, prefix + "NEXT_ID")
39
+ self.sample_id = epics_signal_r(float, prefix + "CURRENT_ID_RBV")
38
40
  self.load = epics_signal_x(prefix + "LOAD.PROC")
39
41
  self.program_running = epics_signal_r(bool, prefix + "PROGRAM_RUNNING")
40
42
  self.program_name = epics_signal_r(str, prefix + "PROGRAM_NAME")
@@ -0,0 +1,15 @@
1
+ from enum import Enum
2
+
3
+ from ophyd_async.core import StandardReadable
4
+ from ophyd_async.epics.signal import epics_signal_rw
5
+
6
+
7
+ class ThawerStates(str, Enum):
8
+ OFF = "Off"
9
+ ON = "On"
10
+
11
+
12
+ class Thawer(StandardReadable):
13
+ def __init__(self, prefix: str, name: str = "") -> None:
14
+ self.control = epics_signal_rw(ThawerStates, prefix + ":CTRL")
15
+ super().__init__(name)
@@ -0,0 +1,46 @@
1
+ from bluesky import plan_stubs as bps
2
+ from bluesky import preprocessors as bpp
3
+ from bluesky.utils import make_decorator
4
+ from ophyd_async.core import DirectoryInfo
5
+
6
+ from dodal.common.beamlines import beamline_utils
7
+ from dodal.common.types import MsgGenerator, UpdatingDirectoryProvider
8
+
9
+ DATA_SESSION = "data_session"
10
+ DATA_GROUPS = "data_groups"
11
+
12
+
13
+ def attach_data_session_metadata_wrapper(
14
+ plan: MsgGenerator, provider: UpdatingDirectoryProvider | None = None
15
+ ) -> MsgGenerator:
16
+ """
17
+ Attach data session metadata to the runs within a plan and make it correlate
18
+ with an ophyd-async DirectoryProvider.
19
+
20
+ This updates the directory provider (which in turn makes a call to to a service
21
+ to figure out which scan number we are using for such a scan), and ensures the
22
+ start document contains the correct data session.
23
+
24
+ Args:
25
+ plan: The plan to preprocess
26
+ provider: The directory provider that participating detectors are aware of.
27
+
28
+ Returns:
29
+ MsgGenerator: A plan
30
+
31
+ Yields:
32
+ Iterator[Msg]: Plan messages
33
+ """
34
+ if provider is None:
35
+ provider = beamline_utils.get_directory_provider()
36
+ yield from bps.wait_for([provider.update])
37
+ directory_info: DirectoryInfo = provider()
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
+ data_session = directory_info.prefix.removesuffix("-")
41
+ yield from bpp.inject_md_wrapper(plan, md={DATA_SESSION: data_session})
42
+
43
+
44
+ attach_data_session_metadata_decorator = make_decorator(
45
+ attach_data_session_metadata_wrapper
46
+ )
@@ -1,162 +0,0 @@
1
- import threading
2
-
3
- from bluesky.plan_stubs import mv
4
- from ophyd import (
5
- Component,
6
- Device,
7
- EpicsSignal,
8
- EpicsSignalRO,
9
- EpicsSignalWithRBV,
10
- Signal,
11
- )
12
- from ophyd.status import DeviceStatus, StatusBase
13
-
14
- from dodal.devices.fast_grid_scan import GridScanParamsCommon
15
- from dodal.devices.status import await_value
16
-
17
-
18
- class GridScanCompleteStatus(DeviceStatus):
19
- """
20
- A Status for the grid scan completion
21
- Progress bar functionality has been removed for now in the panda fast grid scan
22
- """
23
-
24
- def __init__(self, *args, **kwargs):
25
- super().__init__(*args, **kwargs)
26
-
27
- self.device.status.subscribe(self._running_changed)
28
-
29
- def _running_changed(self, value=None, old_value=None, **kwargs):
30
- if (old_value == 1) and (value == 0):
31
- self.set_finished()
32
- self.clean_up()
33
-
34
- def clean_up(self):
35
- self.device.status.clear_sub(self._running_changed)
36
-
37
-
38
- class PandAGridScanParams(GridScanParamsCommon):
39
- """
40
- Holder class for the parameters of a grid scan in a similar
41
- layout to EPICS. These params are used for the panda-triggered
42
- constant motion grid scan
43
-
44
- Motion program will do a grid in x-y then rotate omega +90 and perform
45
- a grid in x-z.
46
-
47
- The grid specified is where data is taken e.g. it can be assumed the first frame is
48
- at x_start, y1_start, z1_start and subsequent frames are N*step_size away.
49
- """
50
-
51
- run_up_distance_mm: float = 0.17
52
-
53
-
54
- class PandAFastGridScan(Device):
55
- """This is similar to the regular FastGridScan device. It has two extra PVs: runup distance and time between x steps.
56
- Dwell time is not moved in this scan.
57
- """
58
-
59
- x_steps: EpicsSignalWithRBV = Component(EpicsSignalWithRBV, "X_NUM_STEPS")
60
- y_steps: EpicsSignalWithRBV = Component(EpicsSignalWithRBV, "Y_NUM_STEPS")
61
- z_steps: EpicsSignalWithRBV = Component(EpicsSignalWithRBV, "Z_NUM_STEPS")
62
-
63
- x_step_size: EpicsSignalWithRBV = Component(EpicsSignalWithRBV, "X_STEP_SIZE")
64
- y_step_size: EpicsSignalWithRBV = Component(EpicsSignalWithRBV, "Y_STEP_SIZE")
65
- z_step_size: EpicsSignalWithRBV = Component(EpicsSignalWithRBV, "Z_STEP_SIZE")
66
-
67
- # This value is fixed by the time between X steps detector deadtime. The only reason it is a PV
68
- # Is so the value can be read by the motion program in the PPMAC
69
- time_between_x_steps_ms = Component(EpicsSignalWithRBV, "TIME_BETWEEN_X_STEPS")
70
-
71
- run_up_distance: EpicsSignalWithRBV = Component(EpicsSignal, "RUNUP_DISTANCE")
72
-
73
- x_start: EpicsSignalWithRBV = Component(EpicsSignalWithRBV, "X_START")
74
- y1_start: EpicsSignalWithRBV = Component(EpicsSignalWithRBV, "Y_START")
75
- y2_start: EpicsSignalWithRBV = Component(EpicsSignalWithRBV, "Y2_START")
76
- z1_start: EpicsSignalWithRBV = Component(EpicsSignalWithRBV, "Z_START")
77
- z2_start: EpicsSignalWithRBV = Component(EpicsSignalWithRBV, "Z2_START")
78
-
79
- position_counter: EpicsSignalRO = Component(EpicsSignalRO, "Y_COUNTER")
80
- scan_invalid: EpicsSignalRO = Component(EpicsSignalRO, "SCAN_INVALID")
81
-
82
- run_cmd: EpicsSignal = Component(EpicsSignal, "RUN.PROC")
83
- stop_cmd: EpicsSignal = Component(EpicsSignal, "STOP.PROC")
84
- status: EpicsSignalRO = Component(EpicsSignalRO, "SCAN_STATUS")
85
-
86
- expected_images: Signal = Component(Signal)
87
-
88
- # Kickoff timeout in seconds
89
- KICKOFF_TIMEOUT: float = 5.0
90
-
91
- def __init__(self, *args, **kwargs):
92
- super().__init__(*args, **kwargs)
93
-
94
- def set_expected_images(*_, **__):
95
- x, y, z = self.x_steps.get(), self.y_steps.get(), self.z_steps.get()
96
- first_grid = x * y
97
- second_grid = x * z
98
- self.expected_images.put(first_grid + second_grid)
99
-
100
- self.x_steps.subscribe(set_expected_images)
101
- self.y_steps.subscribe(set_expected_images)
102
- self.z_steps.subscribe(set_expected_images)
103
-
104
- def is_invalid(self) -> bool:
105
- if "GONP" in self.scan_invalid.pvname:
106
- return False
107
- return self.scan_invalid.get()
108
-
109
- def kickoff(self) -> StatusBase:
110
- # Check running already here?
111
- st = DeviceStatus(device=self, timeout=self.KICKOFF_TIMEOUT)
112
-
113
- def scan():
114
- try:
115
- self.log.debug("Running scan")
116
- self.run_cmd.put(1)
117
- self.log.debug("Waiting for scan to start")
118
- await_value(self.status, 1).wait()
119
- st.set_finished()
120
- except Exception as e:
121
- st.set_exception(e)
122
-
123
- threading.Thread(target=scan, daemon=True).start()
124
- return st
125
-
126
- def complete(self) -> DeviceStatus:
127
- return GridScanCompleteStatus(self)
128
-
129
- def collect(self):
130
- return {}
131
-
132
- def describe_collect(self):
133
- return {}
134
-
135
-
136
- def set_fast_grid_scan_params(scan: PandAFastGridScan, params: PandAGridScanParams):
137
- yield from mv(
138
- scan.x_steps,
139
- params.x_steps,
140
- scan.y_steps,
141
- params.y_steps,
142
- scan.z_steps,
143
- params.z_steps,
144
- scan.x_step_size,
145
- params.x_step_size,
146
- scan.y_step_size,
147
- params.y_step_size,
148
- scan.z_step_size,
149
- params.z_step_size,
150
- scan.x_start,
151
- params.x_start,
152
- scan.y1_start,
153
- params.y1_start,
154
- scan.y2_start,
155
- params.y2_start,
156
- scan.z1_start,
157
- params.z1_start,
158
- scan.z2_start,
159
- params.z2_start,
160
- scan.run_up_distance,
161
- params.run_up_distance_mm,
162
- )