dls-dodal 1.69.0__py3-none-any.whl → 2.0.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.
Files changed (282) hide show
  1. {dls_dodal-1.69.0.dist-info → dls_dodal-2.0.0.dist-info}/METADATA +1 -1
  2. dls_dodal-2.0.0.dist-info/RECORD +354 -0
  3. {dls_dodal-1.69.0.dist-info → dls_dodal-2.0.0.dist-info}/WHEEL +1 -1
  4. dodal/_version.py +2 -2
  5. dodal/beamlines/__init__.py +10 -17
  6. dodal/beamlines/adsim.py +17 -17
  7. dodal/beamlines/b01_1.py +11 -0
  8. dodal/beamlines/b07.py +17 -21
  9. dodal/beamlines/b07_1.py +20 -22
  10. dodal/beamlines/b07_shared.py +12 -0
  11. dodal/beamlines/b16.py +1 -1
  12. dodal/beamlines/b21.py +15 -6
  13. dodal/beamlines/i02_1.py +3 -3
  14. dodal/beamlines/i02_2.py +1 -1
  15. dodal/beamlines/i03.py +4 -4
  16. dodal/beamlines/i04.py +16 -8
  17. dodal/beamlines/i05.py +7 -45
  18. dodal/beamlines/i05_1.py +4 -13
  19. dodal/beamlines/i05_shared.py +51 -0
  20. dodal/beamlines/i06_1.py +7 -5
  21. dodal/beamlines/{i06.py → i06_shared.py} +25 -14
  22. dodal/beamlines/i07.py +14 -16
  23. dodal/beamlines/i09.py +54 -51
  24. dodal/beamlines/i09_1.py +25 -64
  25. dodal/beamlines/i09_1_shared.py +61 -0
  26. dodal/beamlines/i09_2.py +6 -100
  27. dodal/beamlines/i09_2_shared.py +110 -0
  28. dodal/beamlines/i10.py +60 -54
  29. dodal/beamlines/i10_1.py +99 -10
  30. dodal/beamlines/{i10_optics.py → i10_shared.py} +80 -66
  31. dodal/beamlines/i11.py +31 -18
  32. dodal/beamlines/i13_1.py +1 -1
  33. dodal/beamlines/i15.py +6 -6
  34. dodal/beamlines/i15_1.py +6 -6
  35. dodal/beamlines/i17.py +37 -28
  36. dodal/beamlines/i18.py +3 -4
  37. dodal/beamlines/i19_1.py +95 -34
  38. dodal/beamlines/i19_2.py +68 -52
  39. dodal/beamlines/i19_optics.py +26 -13
  40. dodal/beamlines/i20_1.py +6 -14
  41. dodal/beamlines/i21.py +35 -28
  42. dodal/beamlines/i22.py +19 -4
  43. dodal/beamlines/i23.py +1 -2
  44. dodal/beamlines/i24.py +11 -10
  45. dodal/beamlines/k07.py +99 -5
  46. dodal/beamlines/p38.py +3 -3
  47. dodal/beamlines/p60.py +28 -17
  48. dodal/beamlines/p99.py +16 -15
  49. dodal/beamlines/training_rig.py +20 -12
  50. dodal/cli.py +36 -2
  51. dodal/common/beamlines/beamline_parameters.py +2 -1
  52. dodal/common/beamlines/beamline_utils.py +11 -9
  53. dodal/common/beamlines/commissioning_mode.py +6 -3
  54. dodal/common/coordination.py +12 -14
  55. dodal/common/crystal_metadata.py +5 -8
  56. dodal/common/device_utils.py +4 -3
  57. dodal/common/maths.py +28 -40
  58. dodal/common/udc_directory_provider.py +13 -8
  59. dodal/common/visit.py +18 -21
  60. dodal/common/watcher_utils.py +13 -12
  61. dodal/device_manager.py +94 -54
  62. dodal/devices/aperturescatterguard.py +26 -27
  63. dodal/devices/areadetector/plugins/cam.py +1 -3
  64. dodal/devices/areadetector/plugins/mjpg.py +6 -5
  65. dodal/devices/attenuator/attenuator.py +12 -11
  66. dodal/devices/beamlines/b07/__init__.py +3 -0
  67. dodal/devices/{b07_1 → beamlines/b07_1}/__init__.py +2 -2
  68. dodal/devices/{b07_1 → beamlines/b07_1}/ccmc.py +5 -10
  69. dodal/devices/{b16 → beamlines/b16}/detector.py +2 -3
  70. dodal/devices/{i02_1 → beamlines/i02_1}/fast_grid_scan.py +2 -3
  71. dodal/devices/{i02_1 → beamlines/i02_1}/sample_motors.py +1 -1
  72. dodal/devices/{i03 → beamlines/i03}/beamsize.py +11 -7
  73. dodal/devices/{i03 → beamlines/i03}/dcm.py +1 -2
  74. dodal/devices/{i03 → beamlines/i03}/undulator_dcm.py +4 -5
  75. dodal/devices/beamlines/i04/beam_centre.py +151 -0
  76. dodal/devices/{i04 → beamlines/i04}/beamsize.py +11 -7
  77. dodal/devices/{i04 → beamlines/i04}/murko_results.py +5 -5
  78. dodal/devices/{i04 → beamlines/i04}/transfocator.py +10 -15
  79. dodal/devices/beamlines/i05/__init__.py +3 -0
  80. dodal/devices/beamlines/i06_shared/__init__.py +3 -0
  81. dodal/devices/beamlines/i06_shared/i06_enum.py +7 -0
  82. dodal/devices/{i07 → beamlines/i07}/dcm.py +2 -3
  83. dodal/devices/{i07 → beamlines/i07}/id.py +8 -9
  84. dodal/devices/beamlines/i09/__init__.py +3 -0
  85. dodal/devices/{i09_1_shared → beamlines/i09_1_shared}/hard_energy.py +5 -6
  86. dodal/devices/{i09_1_shared → beamlines/i09_1_shared}/hard_undulator_functions.py +19 -16
  87. dodal/devices/{i10 → beamlines/i10}/diagnostics.py +4 -3
  88. dodal/devices/{i10 → beamlines/i10}/i10_apple2.py +31 -45
  89. dodal/devices/{i10 → beamlines/i10}/rasor/rasor_current_amp.py +1 -24
  90. dodal/devices/{i10 → beamlines/i10}/rasor/rasor_motors.py +2 -2
  91. dodal/devices/{i10 → beamlines/i10}/slits.py +5 -3
  92. dodal/devices/beamlines/i10_1/__init__.py +9 -0
  93. dodal/devices/beamlines/i10_1/electromagnet/magnet.py +16 -0
  94. dodal/devices/beamlines/i10_1/electromagnet/stages.py +14 -0
  95. dodal/devices/beamlines/i10_1/scaler_cards.py +13 -0
  96. dodal/devices/{i11 → beamlines/i11}/cyberstar_blower.py +1 -1
  97. dodal/devices/{i11 → beamlines/i11}/diff_stages.py +4 -6
  98. dodal/devices/{i11 → beamlines/i11}/mythen.py +3 -4
  99. dodal/devices/{i11 → beamlines/i11}/nx100robot.py +6 -6
  100. dodal/devices/{i11 → beamlines/i11}/spinner.py +1 -1
  101. dodal/devices/{i13_1 → beamlines/i13_1}/merlin.py +1 -1
  102. dodal/devices/{i15 → beamlines/i15}/dcm.py +1 -2
  103. dodal/devices/{i15 → beamlines/i15}/focussing_mirror.py +5 -5
  104. dodal/devices/{i15 → beamlines/i15}/jack.py +2 -2
  105. dodal/devices/{i15 → beamlines/i15}/multilayer_mirror.py +1 -1
  106. dodal/devices/{i17 → beamlines/i17}/i17_apple2.py +10 -16
  107. dodal/devices/{i18 → beamlines/i18}/diode.py +1 -1
  108. dodal/devices/{i19 → beamlines/i19}/access_controlled/attenuator_motor_squad.py +12 -8
  109. dodal/devices/{i19 → beamlines/i19}/access_controlled/blueapi_device.py +16 -15
  110. dodal/devices/beamlines/i19/access_controlled/piezo_control.py +72 -0
  111. dodal/devices/{i19 → beamlines/i19}/access_controlled/shutter.py +11 -9
  112. dodal/devices/{i19 → beamlines/i19}/backlight.py +3 -1
  113. dodal/devices/{i19 → beamlines/i19}/mapt_configuration.py +2 -1
  114. dodal/devices/{i19 → beamlines/i19}/pin_col_stages.py +11 -8
  115. dodal/devices/beamlines/i19/pin_tip.py +32 -0
  116. dodal/devices/beamlines/i21/__init__.py +3 -0
  117. dodal/devices/{i22 → beamlines/i22}/dcm.py +1 -2
  118. dodal/devices/{i22 → beamlines/i22}/fswitch.py +1 -3
  119. dodal/devices/{i22 → beamlines/i22}/nxsas.py +5 -4
  120. dodal/devices/{i24 → beamlines/i24}/beam_center.py +1 -1
  121. dodal/devices/{i24 → beamlines/i24}/beamstop.py +2 -2
  122. dodal/devices/{i24 → beamlines/i24}/commissioning_jungfrau.py +3 -2
  123. dodal/devices/{i24 → beamlines/i24}/dcm.py +1 -3
  124. dodal/devices/{i24 → beamlines/i24}/dual_backlight.py +3 -3
  125. dodal/devices/{i24 → beamlines/i24}/pmac.py +9 -7
  126. dodal/devices/{p60 → beamlines/p60}/lab_xray_source.py +1 -1
  127. dodal/devices/beamlines/p99/__init__.py +0 -0
  128. dodal/devices/{p99 → beamlines/p99}/andor2_point.py +11 -15
  129. dodal/devices/bimorph_mirror.py +22 -20
  130. dodal/devices/collimation_table.py +3 -2
  131. dodal/devices/common_dcm.py +30 -20
  132. dodal/devices/controllers.py +2 -2
  133. dodal/devices/cryostream.py +8 -0
  134. dodal/devices/current_amplifiers/current_amplifier.py +16 -18
  135. dodal/devices/current_amplifiers/current_amplifier_detector.py +9 -10
  136. dodal/devices/current_amplifiers/femto.py +8 -9
  137. dodal/devices/current_amplifiers/sr570.py +16 -16
  138. dodal/devices/current_amplifiers/struck_scaler_counter.py +5 -5
  139. dodal/devices/detector/det_resolution.py +9 -8
  140. dodal/devices/detector/detector.py +4 -2
  141. dodal/devices/diamond_filter.py +3 -4
  142. dodal/devices/eiger.py +3 -3
  143. dodal/devices/eiger_odin.py +1 -1
  144. dodal/devices/electron_analyser/base/base_controller.py +13 -13
  145. dodal/devices/electron_analyser/base/base_detector.py +15 -20
  146. dodal/devices/electron_analyser/base/base_driver_io.py +39 -46
  147. dodal/devices/electron_analyser/base/base_region.py +27 -30
  148. dodal/devices/electron_analyser/base/base_util.py +18 -16
  149. dodal/devices/electron_analyser/base/energy_sources.py +13 -19
  150. dodal/devices/eurotherm.py +3 -2
  151. dodal/devices/fast_grid_scan.py +31 -34
  152. dodal/devices/fast_shutter.py +24 -21
  153. dodal/devices/flux.py +1 -1
  154. dodal/devices/focusing_mirror.py +29 -11
  155. dodal/devices/hutch_shutter.py +6 -6
  156. dodal/devices/insertion_device/__init__.py +8 -0
  157. dodal/devices/insertion_device/apple2_controller.py +51 -60
  158. dodal/devices/insertion_device/apple2_undulator.py +40 -64
  159. dodal/devices/insertion_device/apple_knot_controller.py +222 -0
  160. dodal/devices/insertion_device/energy.py +100 -27
  161. dodal/devices/insertion_device/energy_motor_lookup.py +20 -27
  162. dodal/devices/insertion_device/lookup_table_models.py +45 -50
  163. dodal/devices/insertion_device/polarisation.py +1 -1
  164. dodal/devices/ipin.py +1 -1
  165. dodal/devices/linkam3.py +7 -5
  166. dodal/devices/motors.py +107 -19
  167. dodal/devices/mx_phase1/beamstop.py +2 -4
  168. dodal/devices/oav/oav_calculations.py +20 -13
  169. dodal/devices/oav/oav_detector.py +28 -23
  170. dodal/devices/oav/oav_parameters.py +4 -9
  171. dodal/devices/oav/oav_to_redis_forwarder.py +22 -18
  172. dodal/devices/oav/pin_image_recognition/__init__.py +4 -6
  173. dodal/devices/oav/pin_image_recognition/manual_test.py +1 -2
  174. dodal/devices/oav/pin_image_recognition/utils.py +30 -32
  175. dodal/devices/oav/snapshots/grid_overlay.py +10 -9
  176. dodal/devices/oav/snapshots/snapshot_image_processing.py +15 -13
  177. dodal/devices/oav/utils.py +9 -12
  178. dodal/devices/p45.py +3 -9
  179. dodal/devices/pgm.py +8 -14
  180. dodal/devices/pressure_jump_cell.py +93 -32
  181. dodal/devices/qbpm.py +1 -3
  182. dodal/devices/robot.py +12 -4
  183. dodal/devices/s4_slit_gaps.py +1 -1
  184. dodal/devices/selectable_source.py +5 -2
  185. dodal/devices/slits.py +2 -5
  186. dodal/devices/smargon.py +2 -3
  187. dodal/devices/temperture_controller/lakeshore/lakeshore.py +38 -64
  188. dodal/devices/temperture_controller/lakeshore/lakeshore_io.py +21 -35
  189. dodal/devices/tetramm.py +7 -7
  190. dodal/devices/turbo_slit.py +8 -7
  191. dodal/devices/undulator.py +42 -56
  192. dodal/devices/util/adjuster_plans.py +2 -3
  193. dodal/devices/util/epics_util.py +10 -10
  194. dodal/devices/util/lookup_tables.py +17 -18
  195. dodal/devices/v2f.py +2 -3
  196. dodal/devices/watsonmarlow323_pump.py +1 -1
  197. dodal/devices/xbpm_feedback.py +3 -2
  198. dodal/devices/xspress3/xspress3.py +8 -11
  199. dodal/devices/xspress3/xspress3_channel.py +3 -6
  200. dodal/devices/zebra/zebra.py +6 -7
  201. dodal/devices/zebra/zebra_constants_mapping.py +11 -7
  202. dodal/devices/zebra/zebra_controlled_shutter.py +2 -1
  203. dodal/devices/zocalo/zocalo_interaction.py +14 -14
  204. dodal/devices/zocalo/zocalo_results.py +33 -33
  205. dodal/log.py +23 -20
  206. dodal/plan_stubs/check_topup.py +15 -15
  207. dodal/plan_stubs/data_session.py +6 -6
  208. dodal/plan_stubs/motor_utils.py +22 -18
  209. dodal/plan_stubs/pressure_jump_cell.py +18 -0
  210. dodal/plan_stubs/wrapped.py +40 -55
  211. dodal/plans/bimorph.py +63 -52
  212. dodal/plans/device_setup_plans/__init__.py +5 -0
  213. dodal/plans/device_setup_plans/setup_pin_tip_params.py +63 -0
  214. dodal/plans/preprocessors/verify_undulator_gap.py +10 -8
  215. dodal/plans/spec_path.py +3 -5
  216. dodal/plans/verify_undulator_gap.py +1 -2
  217. dodal/plans/wrapped.py +4 -3
  218. dodal/testing/electron_analyser/device_factory.py +5 -7
  219. dodal/testing/fixtures/devices/apple2.py +38 -0
  220. dodal/testing/fixtures/run_engine.py +3 -7
  221. dodal/testing/fixtures/utils.py +1 -2
  222. dodal/utils.py +60 -58
  223. dls_dodal-1.69.0.dist-info/RECORD +0 -338
  224. dodal/beamline_specific_utils/i05_shared.py +0 -14
  225. dodal/devices/b07/__init__.py +0 -3
  226. dodal/devices/i04/beam_centre.py +0 -84
  227. dodal/devices/i05/__init__.py +0 -3
  228. dodal/devices/i09/__init__.py +0 -3
  229. dodal/devices/i21/__init__.py +0 -5
  230. {dls_dodal-1.69.0.dist-info → dls_dodal-2.0.0.dist-info}/entry_points.txt +0 -0
  231. {dls_dodal-1.69.0.dist-info → dls_dodal-2.0.0.dist-info}/licenses/LICENSE +0 -0
  232. {dls_dodal-1.69.0.dist-info → dls_dodal-2.0.0.dist-info}/top_level.txt +0 -0
  233. /dodal/{beamline_specific_utils → devices/beamlines}/__init__.py +0 -0
  234. /dodal/devices/{b07 → beamlines/b07}/enums.py +0 -0
  235. /dodal/devices/{b07_1 → beamlines/b07_1}/enums.py +0 -0
  236. /dodal/devices/{b16 → beamlines/b16}/__init__.py +0 -0
  237. /dodal/devices/{i02_1 → beamlines/i02_1}/__init__.py +0 -0
  238. /dodal/devices/{i02_2 → beamlines/i02_2}/__init__.py +0 -0
  239. /dodal/devices/{i03 → beamlines/i03}/__init__.py +0 -0
  240. /dodal/devices/{i03 → beamlines/i03}/constants.py +0 -0
  241. /dodal/devices/{i04 → beamlines/i04}/__init__.py +0 -0
  242. /dodal/devices/{i04 → beamlines/i04}/constants.py +0 -0
  243. /dodal/devices/{i04 → beamlines/i04}/max_pixel.py +0 -0
  244. /dodal/devices/{i05 → beamlines/i05}/enums.py +0 -0
  245. /dodal/devices/{i07 → beamlines/i07}/__init__.py +0 -0
  246. /dodal/devices/{i09 → beamlines/i09}/enums.py +0 -0
  247. /dodal/devices/{i09_1 → beamlines/i09_1}/__init__.py +0 -0
  248. /dodal/devices/{i09_1 → beamlines/i09_1}/enums.py +0 -0
  249. /dodal/devices/{i09_1_shared → beamlines/i09_1_shared}/__init__.py +0 -0
  250. /dodal/devices/{i09_2_shared → beamlines/i09_2_shared}/__init__.py +0 -0
  251. /dodal/devices/{i09_2_shared → beamlines/i09_2_shared}/i09_apple2.py +0 -0
  252. /dodal/devices/{i10 → beamlines/i10}/__init__.py +0 -0
  253. /dodal/devices/{i10 → beamlines/i10}/i10_setting_data.py +0 -0
  254. /dodal/devices/{i10 → beamlines/i10}/mirrors.py +0 -0
  255. /dodal/devices/{i10 → beamlines/i10}/rasor/__init__.py +0 -0
  256. /dodal/devices/{i10 → beamlines/i10}/rasor/rasor_scaler_cards.py +0 -0
  257. /dodal/devices/{i11 → beamlines/i10_1/electromagnet}/__init__.py +0 -0
  258. /dodal/devices/{i13_1 → beamlines/i11}/__init__.py +0 -0
  259. /dodal/devices/{i15 → beamlines/i13_1}/__init__.py +0 -0
  260. /dodal/devices/{i13_1 → beamlines/i13_1}/merlin_controller.py +0 -0
  261. /dodal/devices/{i17 → beamlines/i15}/__init__.py +0 -0
  262. /dodal/devices/{i15 → beamlines/i15}/laue.py +0 -0
  263. /dodal/devices/{i15 → beamlines/i15}/motors.py +0 -0
  264. /dodal/devices/{i15 → beamlines/i15}/rail.py +0 -0
  265. /dodal/devices/{i18 → beamlines/i17}/__init__.py +0 -0
  266. /dodal/devices/{i19 → beamlines/i18}/__init__.py +0 -0
  267. /dodal/devices/{i18 → beamlines/i18}/kb_mirror.py +0 -0
  268. /dodal/devices/{i19/access_controlled → beamlines/i19}/__init__.py +0 -0
  269. /dodal/devices/{i20_1 → beamlines/i19/access_controlled}/__init__.py +0 -0
  270. /dodal/devices/{i19 → beamlines/i19}/access_controlled/hutch_access.py +0 -0
  271. /dodal/devices/{i19 → beamlines/i19}/beamstop.py +0 -0
  272. /dodal/devices/{i19 → beamlines/i19}/diffractometer.py +0 -0
  273. /dodal/devices/{i22 → beamlines/i20_1}/__init__.py +0 -0
  274. /dodal/devices/{i21 → beamlines/i21}/enums.py +0 -0
  275. /dodal/devices/{i24 → beamlines/i22}/__init__.py +0 -0
  276. /dodal/devices/{p99 → beamlines/i24}/__init__.py +0 -0
  277. /dodal/devices/{i24 → beamlines/i24}/aperture.py +0 -0
  278. /dodal/devices/{i24 → beamlines/i24}/focus_mirrors.py +0 -0
  279. /dodal/devices/{i24 → beamlines/i24}/vgonio.py +0 -0
  280. /dodal/devices/{p60 → beamlines/p60}/__init__.py +0 -0
  281. /dodal/devices/{p60 → beamlines/p60}/enums.py +0 -0
  282. /dodal/devices/{p99 → beamlines/p99}/sample_stage.py +0 -0
@@ -27,7 +27,7 @@ DEFAULT_SETTLE_TIME_S = 60
27
27
 
28
28
 
29
29
  class MirrorType(StrictEnum):
30
- """See https://manual.nexusformat.org/classes/base_classes/NXmirror.html"""
30
+ """See https://manual.nexusformat.org/classes/base_classes/NXmirror.html."""
31
31
 
32
32
  SINGLE = "single"
33
33
  MULTI = "multi"
@@ -53,8 +53,9 @@ class MirrorVoltageDemand(StrictEnum):
53
53
 
54
54
 
55
55
  class SingleMirrorVoltage(Device):
56
- """Abstract the bimorph mirror voltage PVs into a single device that can be set asynchronously and returns when
57
- the demanded voltage setpoint is accepted, without blocking the caller as this process can take significant time.
56
+ """Abstract the bimorph mirror voltage PVs into a single device that can be set
57
+ asynchronously and returns when the demanded voltage setpoint is accepted, without
58
+ blocking the caller as this process can take significant time.
58
59
  """
59
60
 
60
61
  def __init__(self, prefix: str, name: str = ""):
@@ -66,11 +67,14 @@ class SingleMirrorVoltage(Device):
66
67
  @AsyncStatus.wrap
67
68
  async def set(self, value, *args, **kwargs):
68
69
  """Combine the following operations into a single set:
69
- 1. apply the value to the setpoint PV
70
- 3. Wait until demand is accepted
71
- 4. When either demand is accepted or DEFAULT_SETTLE_TIME expires, signal the result on the Status
72
- """
73
70
 
71
+ 1. apply the value to the setpoint PV.
72
+
73
+ 3. Wait until demand is accepted.
74
+
75
+ 4. When either demand is accepted or DEFAULT_SETTLE_TIME expires, signal the
76
+ result on the Status.
77
+ """ # noqa D415
74
78
  setpoint_v = self._setpoint_v
75
79
  demand_accepted = self._demand_accepted
76
80
 
@@ -136,7 +140,7 @@ class MirrorVoltages(StandardReadable):
136
140
 
137
141
 
138
142
  class SimpleMirror(XYPitchStage):
139
- """Simple Focusing Mirror"""
143
+ """Simple Focusing Mirror."""
140
144
 
141
145
  def __init__(
142
146
  self,
@@ -165,7 +169,7 @@ class SimpleMirror(XYPitchStage):
165
169
 
166
170
 
167
171
  class FocusingMirror(StandardReadable):
168
- """Focusing Mirror"""
172
+ """Focusing Mirror."""
169
173
 
170
174
  def __init__(
171
175
  self,
@@ -202,7 +206,8 @@ class FocusingMirror(StandardReadable):
202
206
 
203
207
  class FocusingMirrorWithStripes(FocusingMirror):
204
208
  """A focusing mirror where the stripe material can be changed. This is usually done
205
- based on the energy of the beamline."""
209
+ based on the energy of the beamline.
210
+ """
206
211
 
207
212
  def __init__(self, prefix: str, name: str = "", *args, **kwargs):
208
213
  self.stripe = epics_signal_rw(MirrorStripe, prefix + "STRP:DVAL")
@@ -212,9 +217,22 @@ class FocusingMirrorWithStripes(FocusingMirror):
212
217
  super().__init__(prefix, name, *args, **kwargs)
213
218
 
214
219
  def energy_to_stripe(self, energy_kev) -> MirrorStripeConfiguration:
215
- """Return the stripe, yaw angle and lateral position for the specified energy"""
220
+ """Return the stripe, yaw angle and lateral position for the specified energy."""
216
221
  # In future, this should be configurable per-mirror
217
222
  if energy_kev < 7:
218
223
  return {"stripe": MirrorStripe.BARE, "yaw_mrad": 6.2, "lat_mm": 0.0}
219
224
  else:
220
225
  return {"stripe": MirrorStripe.RHODIUM, "yaw_mrad": 0.0, "lat_mm": 10.0}
226
+
227
+
228
+ class FocusingMirrorWithPiezo(FocusingMirror):
229
+ """A focusing mirror which also has a piezoelectric actuator.
230
+ A voltage can be applied to the piezo to steer the beam by making the material
231
+ shrink or expand.
232
+ """
233
+
234
+ def __init__(self, prefix: str, name: str = "", *args, **kwargs):
235
+ with self.add_children_as_readables():
236
+ self.piezo = epics_signal_rw(float, f"{prefix}AOFPITCH")
237
+ self.piezo_rbv = epics_signal_r(float, f"{prefix}AOFPITCH:RBV")
238
+ super().__init__(prefix, name, *args, **kwargs)
@@ -58,14 +58,14 @@ class HutchInterlock(StandardReadable):
58
58
  class HutchShutter(StandardReadable, Movable[ShutterDemand]):
59
59
  """Device to operate the hutch shutter.
60
60
 
61
- When a demand is sent, the device should first check the hutch status \
62
- and raise an error if it's not interlocked (searched and locked), meaning it's not \
61
+ When a demand is sent, the device should first check the hutch status
62
+ and raise an error if it's not interlocked (searched and locked), meaning it's not
63
63
  safe to operate the shutter.
64
64
 
65
- If the requested shutter position is "Open", the shutter control PV should first \
66
- go to "Reset" and then move to "Open". This is because before opening the hutch \
67
- shutter, the interlock status PV (`-PS-SHTR-01:ILKSTA`) will show as `failed` until \
68
- the hutch shutter is reset. This will set the interlock status to `OK`, allowing \
65
+ If the requested shutter position is "Open", the shutter control PV should first
66
+ go to "Reset" and then move to "Open". This is because before opening the hutch
67
+ shutter, the interlock status PV (`-PS-SHTR-01:ILKSTA`) will show as `failed` until
68
+ the hutch shutter is reset. This will set the interlock status to `OK`, allowing
69
69
  for shutter operations. Until this step is done, the hutch shutter can't be opened.
70
70
  The reset is not needed for closing the shutter.
71
71
  """
@@ -2,6 +2,7 @@ from .apple2_controller import (
2
2
  MAXIMUM_MOVE_TIME,
3
3
  Apple2Controller,
4
4
  Apple2EnforceLHMoveController,
5
+ Apple2Type,
5
6
  EnergyMotorConvertor,
6
7
  )
7
8
  from .apple2_undulator import (
@@ -17,6 +18,10 @@ from .apple2_undulator import (
17
18
  UndulatorPhaseAxes,
18
19
  UnstoppableMotor,
19
20
  )
21
+ from .apple_knot_controller import (
22
+ AppleKnotController,
23
+ AppleKnotPathFinder,
24
+ )
20
25
  from .energy import BeamEnergy, InsertionDeviceEnergy, InsertionDeviceEnergyBase
21
26
  from .energy_motor_lookup import (
22
27
  ConfigServerEnergyMotorLookup,
@@ -33,8 +38,11 @@ from .polarisation import InsertionDevicePolarisation
33
38
 
34
39
  __all__ = [
35
40
  "Apple2",
41
+ "Apple2Type",
36
42
  "Apple2Controller",
37
43
  "Apple2EnforceLHMoveController",
44
+ "AppleKnotController",
45
+ "AppleKnotPathFinder",
38
46
  "UndulatorGap",
39
47
  "UndulatorPhaseAxes",
40
48
  "UndulatorJawPhase",
@@ -33,7 +33,7 @@ MAXIMUM_GAP_MOTOR_POSITION = 100
33
33
 
34
34
  class EnergyMotorConvertor(Protocol):
35
35
  def __call__(self, energy: float, pol: Pol) -> float:
36
- """Protocol to provide energy to motor position conversion"""
36
+ """Protocol to provide energy to motor position conversion."""
37
37
  ...
38
38
 
39
39
 
@@ -41,44 +41,51 @@ Apple2Type = TypeVar("Apple2Type", bound=Apple2)
41
41
 
42
42
 
43
43
  class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
44
- """
45
-
46
- Abstract base class for controlling an Apple2 undulator device.
44
+ """Abstract base class for controlling an Apple2 undulator device.
47
45
 
48
46
  This class manages the undulator's gap and phase motors, and provides an interface
49
47
  for controlling polarisation and energy settings. It exposes derived signals for
50
48
  energy and polarisation, and handles conversion between energy/polarisation and
51
49
  motor positions via a user-supplied conversion callable.
52
50
 
53
- Attributes
54
- ----------
55
- apple2 : Reference[Apple2Type]
56
- Reference to the Apple2 device containing gap and phase motors.
57
- energy : derived_signal_rw
58
- Derived signal for moving and reading back energy.
59
- polarisation_setpoint : SignalR
60
- Soft signal for the polarisation setpoint.
61
- polarisation : derived_signal_rw
62
- Hardware-backed signal for polarisation readback and control.
63
- gap_energy_to_motor_converter : EnergyMotorConvertor
64
- Callable that converts energy and polarisation to gap motor positions.
65
- phase_energy_to_motor_converter : EnergyMotorConvertor
66
- Callable that converts energy and polarisation to phase motor positions.
67
- maximum_gap_motor_position : float
68
- Maximum allowed position for the gap motor.
69
- maximum_phase_motor_position : float
70
- Maximum allowed position for the raw phase motors.
71
-
72
- Abstract Methods
73
- ----------------
74
- _get_apple2_value(gap: float, phase: float) -> Apple2Val
75
- Abstract method to return the Apple2Val used to set the apple2 with.
76
- Notes
77
- -----
78
- - Subclasses must implement `_get_apple2_value` for beamline-specific logic.
79
- - LH3 polarisation is indistinguishable from LH in hardware; special handling is provided.
80
- - Supports multiple polarisation modes, including linear horizontal (LH), linear vertical (LV),
81
- positive circular (PC), negative circular (NC), and linear arbitrary (LA).
51
+ Attributes:
52
+ apple2 (Reference[Apple2Type]): Reference to the Apple2 device containing gap
53
+ and phase motors.
54
+ energy (derived_signal_rw): Derived signal for moving and reading back energy.
55
+ polarisation_setpoint (SignalR): Soft signal for the polarisation setpoint.
56
+ polarisation (derived_signal_rw): Hardware-backed signal for polarisation
57
+ readback and control.
58
+ gap_energy_to_motor_converter (EnergyMotorConvertor): Callable that converts
59
+ energy and polarisation to gap motor positions.
60
+ phase_energy_to_motor_converter (EnergyMotorConvertor): Callable that converts
61
+ energy and polarisation to phase motor positions.
62
+ maximum_gap_motor_position (float): Maximum allowed position for the gap motor.
63
+ maximum_phase_motor_position (float): Maximum allowed position for the raw phase
64
+ motors.
65
+
66
+ Args:
67
+ apple2 (Apple2): An Apple2 device.
68
+ gap_energy_motor_converter (EnergyMotorConvertor): The callable that handles
69
+ the gap look up table logic for the insertion device.
70
+ phase_energy_motor_converter (EnergyMotorConvertor): The callable that
71
+ handles the phase look up table logic for the insertion device.
72
+ maximum_gap_motor_position (float): Maximum allowed position for the gap motor.
73
+ maximum_phase_motor_position (float): Maximum allowed position for the raw phase
74
+ motors.
75
+ units (str): the units of this device. Defaults to eV.
76
+ name (str): Name of the device.
77
+
78
+ Abstract Methods:
79
+ _get_apple2_value(gap: float, phase: float) -> Apple2Val
80
+ Abstract method to return the Apple2Val used to set the apple2 with.
81
+
82
+ Notes:
83
+ - Subclasses must implement `_get_apple2_value` for beamline-specific logic.
84
+ - LH3 polarisation is indistinguishable from LH in hardware; special handling is
85
+ provided.
86
+ - Supports multiple polarisation modes, including linear horizontal (LH), linear
87
+ vertical (LV), positive circular (PC), negative circular (NC), and linear
88
+ arbitrary (LA).
82
89
 
83
90
  """
84
91
 
@@ -92,21 +99,6 @@ class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
92
99
  units: str = "eV",
93
100
  name: str = "",
94
101
  ) -> None:
95
- """
96
-
97
- Parameters
98
- ----------
99
- apple2: Apple2
100
- An Apple2 device.
101
- gap_energy_motor_converter: EnergyMotorConvertor
102
- The callable that handles the gap look up table logic for the insertion device.
103
- phase_energy_motor_converter: EnergyMotorConvertor
104
- The callable that handles the phase look up table logic for the insertion device.
105
- units: str
106
- the units of this device. Defaults to eV.
107
- name: str
108
- Name of the device.
109
- """
110
102
  self.apple2 = Reference(apple2)
111
103
  self.gap_energy_motor_converter = gap_energy_motor_converter
112
104
  self.phase_energy_motor_converter = phase_energy_motor_converter
@@ -156,8 +148,7 @@ class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
156
148
 
157
149
  @abc.abstractmethod
158
150
  def _get_apple2_value(self, gap: float, phase: float, pol: Pol) -> Apple2Val:
159
- """
160
- This method should be implemented by the beamline specific ID class as the
151
+ """This method should be implemented by the beamline specific ID class as the
161
152
  motor positions will be different for each beamline depending on the
162
153
  undulator design.
163
154
  """
@@ -182,8 +173,7 @@ class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
182
173
  return energy
183
174
 
184
175
  async def _check_and_get_pol_setpoint(self) -> Pol:
185
- """
186
- Check the polarisation setpoint and if it is NONE try to read it from
176
+ """Check the polarisation setpoint and if it is NONE try to read it from
187
177
  hardware.
188
178
  """
189
179
  pol = await self.polarisation_setpoint.get_value()
@@ -246,11 +236,11 @@ class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
246
236
  btm_outer: float,
247
237
  gap: float,
248
238
  ) -> tuple[Pol, float]:
249
- """
250
- Determine polarisation and phase value using motor position patterns.
239
+ """Determine polarisation and phase value using motor position patterns.
251
240
  However there is no way to return lh3 polarisation or higher harmonic setting.
252
- (May be for future one can use the inverse poly to work out the energy and try to match it with the current energy
253
- to workout the polarisation but during my test the inverse poly is too unstable for general use.)
241
+ (May be for future one can use the inverse poly to work out the energy and try
242
+ to match it with the current energy to workout the polarisation but during my
243
+ test the inverse poly is too unstable for general use).
254
244
  """
255
245
  if gap > self.maximum_gap_motor_position:
256
246
  raise RuntimeError(
@@ -266,16 +256,16 @@ class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
266
256
  if (
267
257
  isclose(
268
258
  top_outer,
269
- self.maximum_phase_motor_position,
259
+ btm_inner,
270
260
  abs_tol=ROW_PHASE_MOTOR_TOLERANCE,
271
261
  )
272
262
  and isclose(top_inner, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
263
+ and isclose(btm_outer, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
273
264
  and isclose(
274
- btm_inner,
265
+ abs(btm_inner),
275
266
  self.maximum_phase_motor_position,
276
267
  abs_tol=ROW_PHASE_MOTOR_TOLERANCE,
277
268
  )
278
- and isclose(btm_outer, 0.0, abs_tol=ROW_PHASE_MOTOR_TOLERANCE)
279
269
  ):
280
270
  LOGGER.info("Determined polarisation: LV (Linear Vertical).")
281
271
  return Pol.LV, self.maximum_phase_motor_position
@@ -321,7 +311,8 @@ class Apple2EnforceLHMoveController(
321
311
  However, because of the high forces involved in polarization changes,
322
312
  all movements must be performed using the Linear Horizontal (LH) mode.
323
313
  A look-up table must also be used to determine the highest energy that can
324
- be reached in LH mode."""
314
+ be reached in LH mode.
315
+ """
325
316
 
326
317
  def __init__(
327
318
  self,
@@ -170,19 +170,14 @@ class GapSafeMotorNoStop(UnstoppableMotor, UndulatorBase[float]):
170
170
 
171
171
 
172
172
  class UndulatorGap(GapSafeMotorNoStop):
173
- """Apple 2 undulator gap motor device. With PV corrections."""
173
+ """Apple 2 undulator gap motor device. With PV corrections.
174
174
 
175
- def __init__(self, prefix: str, name: str = ""):
176
- """
177
-
178
- Parameters
179
- ----------
180
- prefix : str
181
- Beamline specific part of the PV
182
- name : str
183
- Name of the Id device
175
+ Args:
176
+ prefix (str): Beamline specific part of the PV
177
+ name (str): Name of the Id device
178
+ """
184
179
 
185
- """
180
+ def __init__(self, prefix: str, name: str = ""):
186
181
  self.set_move = epics_signal_rw(int, prefix + "BLGSETP")
187
182
  # Nothing move until this is set to 1 and it will return to 0 when done.
188
183
  super().__init__(self.set_move, prefix, name)
@@ -201,8 +196,7 @@ class UndulatorGap(GapSafeMotorNoStop):
201
196
 
202
197
  @AsyncStatus.wrap
203
198
  async def prepare(self, value: FlyMotorInfo) -> None:
204
- """
205
- Prepare for a fly scan by moving to the run-up position at max velocity.
199
+ """Prepare for a fly scan by moving to the run-up position at max velocity.
206
200
  Stores fly info for later use in kickoff.
207
201
  """
208
202
  max_velocity, min_velocity, egu = await asyncio.gather(
@@ -228,18 +222,14 @@ class UndulatorGap(GapSafeMotorNoStop):
228
222
 
229
223
 
230
224
  class UndulatorPhaseMotor(UnstoppableMotor):
231
- """Phase motor that will not stop."""
225
+ """Phase motor that will not stop.
232
226
 
233
- def __init__(self, prefix: str, name: str = ""):
234
- """
235
- Parameters
236
- ----------
227
+ Args:
228
+ prefix (str): The setting prefix PV.
229
+ name (str, optional): Name of the Id phase device.
230
+ """
237
231
 
238
- prefix : str
239
- The setting prefix PV.
240
- name : str
241
- Name of the Id phase device
242
- """
232
+ def __init__(self, prefix: str, name: str = ""):
243
233
  motor_pv = f"{prefix}MTR"
244
234
  super().__init__(prefix=motor_pv, name=name)
245
235
  self.user_setpoint = epics_signal_rw(str, prefix + "SET")
@@ -275,10 +265,9 @@ class UndulatorLockedPhaseAxes(SafeUndulatorMover[Apple2PhaseValType]):
275
265
  )
276
266
 
277
267
  async def get_timeout(self) -> float:
268
+ """Get all motor speed, current positions and target positions to calculate
269
+ required timeout.
278
270
  """
279
- Get all motor speed, current positions and target positions to calculate required timeout.
280
- """
281
-
282
271
  timeouts = await asyncio.gather(
283
272
  *[
284
273
  estimate_motor_timeout(
@@ -291,20 +280,19 @@ class UndulatorLockedPhaseAxes(SafeUndulatorMover[Apple2PhaseValType]):
291
280
  )
292
281
  """A 2.0 multiplier is required to prevent premature motor timeouts in phase
293
282
  axes as it is a master-slave system, where the slave's movement,
294
- being dependent on the master, can take up to twice as long to complete.
295
- """
283
+ being dependent on the master, can take up to twice as long to complete."""
296
284
  return np.max(timeouts) * 2.0
297
285
 
298
286
 
299
287
  class UndulatorPhaseAxes(UndulatorLockedPhaseAxes[Apple2PhasesVal]):
300
- """
301
- A collection of 4 phase Motor to make up the full id phase motion. We are using the diamond pv convention.
302
- e.g. top_outer == Q1
303
- top_inner == Q2
304
- btm_inner == q3
305
- btm_outer == q4
288
+ """A collection of 4 phase Motor to make up the full id phase motion. We are using
289
+ the diamond PV convention. e.g.::
306
290
 
307
- """
291
+ top_outer == Q1
292
+ top_inner == Q2
293
+ btm_inner == q3
294
+ btm_outer == q4
295
+ """ # noqa D415
308
296
 
309
297
  def __init__(
310
298
  self,
@@ -333,9 +321,8 @@ class UndulatorPhaseAxes(UndulatorLockedPhaseAxes[Apple2PhasesVal]):
333
321
 
334
322
 
335
323
  class UndulatorJawPhase(SafeUndulatorMover[float]):
336
- """
337
- A JawPhase movable, this is use for moving the jaw phase which is use to control the
338
- linear arbitrary polarisation but only on some of the beamline.
324
+ """A JawPhase movable, this is use for moving the jaw phase which is use to control
325
+ the linear arbitrary polarisation but only on some of the beamline.
339
326
  """
340
327
 
341
328
  def __init__(
@@ -357,8 +344,8 @@ class UndulatorJawPhase(SafeUndulatorMover[float]):
357
344
  await self.jaw_phase.user_setpoint.set(value=str(value))
358
345
 
359
346
  async def get_timeout(self) -> float:
360
- """
361
- Get motor speed, current position and target position to calculate required timeout.
347
+ """Get motor speed, current position and target position to calculate required
348
+ timeout.
362
349
  """
363
350
  return await estimate_motor_timeout(
364
351
  self.jaw_phase.user_setpoint_readback,
@@ -371,29 +358,20 @@ PhaseAxesType = TypeVar("PhaseAxesType", bound=UndulatorLockedPhaseAxes)
371
358
 
372
359
 
373
360
  class Apple2(StandardReadable, Movable[Apple2Val], Generic[PhaseAxesType]):
374
- """
375
- Device representing the combined motor controls for an Apple2 undulator.
376
-
377
- Attributes
378
- ----------
379
- gap : UndulatorGap
380
- The undulator gap motor device.
381
- phase : UndulatorPhaseAxes
382
- The undulator phase axes device, consisting of four phase motors.
361
+ """Device representing the combined motor controls for an Apple2 undulator.
362
+
363
+ Attributes:
364
+ gap (UndulatorGap): The undulator gap motor device.
365
+ phase (UndulatorPhaseAxes): The undulator phase axes device, consisting of four
366
+ phase motors.
367
+
368
+ Args:
369
+ id_gap (UndulatorGap): An UndulatorGap device.
370
+ id_phase (UndulatorPhaseAxes): An UndulatorPhaseAxes device.
371
+ name (str, optional): Name of the device.
383
372
  """
384
373
 
385
374
  def __init__(self, id_gap: UndulatorGap, id_phase: PhaseAxesType, name=""):
386
- """
387
- Parameters
388
- ----------
389
-
390
- id_gap: UndulatorGap
391
- An UndulatorGap device.
392
- id_phase: UndulatorPhaseAxes
393
- An UndulatorPhaseAxes device.
394
- name: str
395
- Name of the device.
396
- """
397
375
  with self.add_children_as_readables():
398
376
  self.gap = Reference(id_gap)
399
377
  self.phase = Reference(id_phase)
@@ -401,11 +379,9 @@ class Apple2(StandardReadable, Movable[Apple2Val], Generic[PhaseAxesType]):
401
379
 
402
380
  @AsyncStatus.wrap
403
381
  async def set(self, id_motor_values: Apple2Val) -> None:
382
+ """Check ID is in a movable state and set all the demand value before moving
383
+ them all at the same time.
404
384
  """
405
- Check ID is in a movable state and set all the demand value before moving them
406
- all at the same time.
407
- """
408
-
409
385
  # Only need to check gap as the phase motors share both status and gate with gap.
410
386
  await self.gap().raise_if_cannot_move()
411
387