dls-dodal 1.68.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 (292) hide show
  1. {dls_dodal-1.68.0.dist-info → dls_dodal-2.0.0.dist-info}/METADATA +1 -31
  2. dls_dodal-2.0.0.dist-info/RECORD +354 -0
  3. {dls_dodal-1.68.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 +40 -33
  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 +17 -45
  14. dodal/beamlines/i02_2.py +6 -12
  15. dodal/beamlines/i03.py +8 -5
  16. dodal/beamlines/i03_supervisor.py +19 -0
  17. dodal/beamlines/i04.py +87 -184
  18. dodal/beamlines/i05.py +9 -39
  19. dodal/beamlines/i05_1.py +4 -13
  20. dodal/beamlines/i05_shared.py +51 -0
  21. dodal/beamlines/i06_1.py +26 -0
  22. dodal/beamlines/{i06.py → i06_shared.py} +25 -14
  23. dodal/beamlines/i07.py +14 -16
  24. dodal/beamlines/i09.py +76 -29
  25. dodal/beamlines/i09_1.py +25 -56
  26. dodal/beamlines/i09_1_shared.py +61 -0
  27. dodal/beamlines/i09_2.py +6 -100
  28. dodal/beamlines/i09_2_shared.py +110 -0
  29. dodal/beamlines/i10.py +60 -54
  30. dodal/beamlines/i10_1.py +99 -10
  31. dodal/beamlines/{i10_optics.py → i10_shared.py} +80 -66
  32. dodal/beamlines/i11.py +31 -18
  33. dodal/beamlines/i13_1.py +1 -1
  34. dodal/beamlines/i15.py +6 -6
  35. dodal/beamlines/i15_1.py +6 -6
  36. dodal/beamlines/i16.py +11 -0
  37. dodal/beamlines/i17.py +37 -28
  38. dodal/beamlines/i18.py +3 -4
  39. dodal/beamlines/i19_1.py +95 -34
  40. dodal/beamlines/i19_2.py +68 -52
  41. dodal/beamlines/i19_optics.py +26 -13
  42. dodal/beamlines/i20_1.py +17 -11
  43. dodal/beamlines/i21.py +44 -29
  44. dodal/beamlines/i22.py +19 -4
  45. dodal/beamlines/i23.py +20 -27
  46. dodal/beamlines/i24.py +64 -113
  47. dodal/beamlines/k07.py +99 -5
  48. dodal/beamlines/p38.py +3 -3
  49. dodal/beamlines/p60.py +35 -14
  50. dodal/beamlines/p99.py +16 -15
  51. dodal/beamlines/training_rig.py +20 -12
  52. dodal/cli.py +36 -2
  53. dodal/common/__init__.py +2 -1
  54. dodal/common/beamlines/beamline_parameters.py +2 -1
  55. dodal/common/beamlines/beamline_utils.py +11 -9
  56. dodal/common/beamlines/commissioning_mode.py +6 -3
  57. dodal/common/coordination.py +12 -14
  58. dodal/common/crystal_metadata.py +5 -8
  59. dodal/common/device_utils.py +4 -3
  60. dodal/common/maths.py +87 -19
  61. dodal/common/udc_directory_provider.py +13 -8
  62. dodal/common/visit.py +18 -21
  63. dodal/common/watcher_utils.py +13 -12
  64. dodal/device_manager.py +94 -54
  65. dodal/devices/aperturescatterguard.py +26 -27
  66. dodal/devices/areadetector/plugins/cam.py +1 -3
  67. dodal/devices/areadetector/plugins/mjpg.py +6 -5
  68. dodal/devices/attenuator/attenuator.py +12 -11
  69. dodal/devices/beamlines/b07/__init__.py +3 -0
  70. dodal/devices/{b07_1 → beamlines/b07_1}/__init__.py +2 -2
  71. dodal/devices/{b07_1 → beamlines/b07_1}/ccmc.py +5 -10
  72. dodal/devices/{b16 → beamlines/b16}/detector.py +2 -3
  73. dodal/devices/{i02_1 → beamlines/i02_1}/fast_grid_scan.py +2 -3
  74. dodal/devices/{i02_1 → beamlines/i02_1}/sample_motors.py +1 -1
  75. dodal/devices/{i03 → beamlines/i03}/beamsize.py +11 -7
  76. dodal/devices/{i03 → beamlines/i03}/dcm.py +1 -2
  77. dodal/devices/{i03 → beamlines/i03}/undulator_dcm.py +4 -5
  78. dodal/devices/beamlines/i04/beam_centre.py +151 -0
  79. dodal/devices/{i04 → beamlines/i04}/beamsize.py +11 -7
  80. dodal/devices/beamlines/i04/max_pixel.py +25 -0
  81. dodal/devices/{i04 → beamlines/i04}/murko_results.py +23 -8
  82. dodal/devices/{i04 → beamlines/i04}/transfocator.py +10 -15
  83. dodal/devices/beamlines/i05/__init__.py +3 -0
  84. dodal/devices/beamlines/i06_shared/__init__.py +3 -0
  85. dodal/devices/beamlines/i06_shared/i06_enum.py +7 -0
  86. dodal/devices/{i07 → beamlines/i07}/dcm.py +2 -3
  87. dodal/devices/{i07 → beamlines/i07}/id.py +8 -9
  88. dodal/devices/beamlines/i09/__init__.py +3 -0
  89. dodal/devices/{i09_1_shared → beamlines/i09_1_shared}/hard_energy.py +5 -6
  90. dodal/devices/{i09_1_shared → beamlines/i09_1_shared}/hard_undulator_functions.py +19 -16
  91. dodal/devices/{i10 → beamlines/i10}/diagnostics.py +4 -3
  92. dodal/devices/{i10 → beamlines/i10}/i10_apple2.py +37 -51
  93. dodal/devices/{i10 → beamlines/i10}/rasor/rasor_current_amp.py +1 -24
  94. dodal/devices/{i10 → beamlines/i10}/rasor/rasor_motors.py +2 -2
  95. dodal/devices/{i10 → beamlines/i10}/slits.py +5 -3
  96. dodal/devices/beamlines/i10_1/__init__.py +9 -0
  97. dodal/devices/beamlines/i10_1/electromagnet/magnet.py +16 -0
  98. dodal/devices/beamlines/i10_1/electromagnet/stages.py +14 -0
  99. dodal/devices/beamlines/i10_1/scaler_cards.py +13 -0
  100. dodal/devices/{i11 → beamlines/i11}/cyberstar_blower.py +1 -1
  101. dodal/devices/{i11 → beamlines/i11}/diff_stages.py +4 -6
  102. dodal/devices/{i11 → beamlines/i11}/mythen.py +3 -4
  103. dodal/devices/{i11 → beamlines/i11}/nx100robot.py +6 -6
  104. dodal/devices/{i11 → beamlines/i11}/spinner.py +1 -1
  105. dodal/devices/{i13_1 → beamlines/i13_1}/merlin.py +1 -1
  106. dodal/devices/{i15 → beamlines/i15}/dcm.py +1 -2
  107. dodal/devices/{i15 → beamlines/i15}/focussing_mirror.py +5 -5
  108. dodal/devices/{i15 → beamlines/i15}/jack.py +2 -2
  109. dodal/devices/{i15 → beamlines/i15}/multilayer_mirror.py +1 -1
  110. dodal/devices/{i17 → beamlines/i17}/i17_apple2.py +16 -22
  111. dodal/devices/{i18 → beamlines/i18}/diode.py +1 -1
  112. dodal/devices/{i19 → beamlines/i19}/access_controlled/attenuator_motor_squad.py +12 -8
  113. dodal/devices/{i19 → beamlines/i19}/access_controlled/blueapi_device.py +16 -15
  114. dodal/devices/beamlines/i19/access_controlled/piezo_control.py +72 -0
  115. dodal/devices/{i19 → beamlines/i19}/access_controlled/shutter.py +11 -9
  116. dodal/devices/{i19 → beamlines/i19}/backlight.py +3 -1
  117. dodal/devices/{i19 → beamlines/i19}/mapt_configuration.py +2 -1
  118. dodal/devices/{i19 → beamlines/i19}/pin_col_stages.py +11 -8
  119. dodal/devices/beamlines/i19/pin_tip.py +32 -0
  120. dodal/devices/beamlines/i21/__init__.py +3 -0
  121. dodal/devices/{i22 → beamlines/i22}/dcm.py +1 -2
  122. dodal/devices/{i22 → beamlines/i22}/fswitch.py +1 -3
  123. dodal/devices/{i22 → beamlines/i22}/nxsas.py +5 -4
  124. dodal/devices/{i24 → beamlines/i24}/beam_center.py +1 -1
  125. dodal/devices/{i24 → beamlines/i24}/beamstop.py +2 -2
  126. dodal/devices/{i24 → beamlines/i24}/commissioning_jungfrau.py +12 -12
  127. dodal/devices/{i24 → beamlines/i24}/dcm.py +1 -3
  128. dodal/devices/{i24 → beamlines/i24}/dual_backlight.py +3 -3
  129. dodal/devices/{i24 → beamlines/i24}/pmac.py +9 -7
  130. dodal/devices/{p60 → beamlines/p60}/lab_xray_source.py +1 -1
  131. dodal/devices/beamlines/p99/__init__.py +0 -0
  132. dodal/devices/{p99 → beamlines/p99}/andor2_point.py +11 -15
  133. dodal/devices/bimorph_mirror.py +22 -20
  134. dodal/devices/collimation_table.py +3 -2
  135. dodal/devices/common_dcm.py +30 -20
  136. dodal/devices/controllers.py +2 -2
  137. dodal/devices/cryostream.py +8 -0
  138. dodal/devices/current_amplifiers/current_amplifier.py +16 -18
  139. dodal/devices/current_amplifiers/current_amplifier_detector.py +9 -10
  140. dodal/devices/current_amplifiers/femto.py +8 -9
  141. dodal/devices/current_amplifiers/sr570.py +16 -16
  142. dodal/devices/current_amplifiers/struck_scaler_counter.py +5 -5
  143. dodal/devices/detector/det_resolution.py +9 -8
  144. dodal/devices/detector/detector.py +4 -2
  145. dodal/devices/diamond_filter.py +3 -4
  146. dodal/devices/eiger.py +32 -17
  147. dodal/devices/eiger_odin.py +1 -1
  148. dodal/devices/electron_analyser/base/__init__.py +3 -3
  149. dodal/devices/electron_analyser/base/base_controller.py +32 -21
  150. dodal/devices/electron_analyser/base/base_detector.py +15 -20
  151. dodal/devices/electron_analyser/base/base_driver_io.py +39 -46
  152. dodal/devices/electron_analyser/base/base_enums.py +0 -5
  153. dodal/devices/electron_analyser/base/base_region.py +29 -31
  154. dodal/devices/electron_analyser/base/base_util.py +18 -16
  155. dodal/devices/electron_analyser/base/energy_sources.py +35 -40
  156. dodal/devices/electron_analyser/specs/specs_detector.py +7 -6
  157. dodal/devices/electron_analyser/vgscienta/vgscienta_detector.py +7 -6
  158. dodal/devices/eurotherm.py +3 -2
  159. dodal/devices/fast_grid_scan.py +31 -34
  160. dodal/devices/fast_shutter.py +125 -39
  161. dodal/devices/flux.py +1 -1
  162. dodal/devices/focusing_mirror.py +29 -11
  163. dodal/devices/hutch_shutter.py +6 -6
  164. dodal/devices/insertion_device/__init__.py +20 -8
  165. dodal/devices/insertion_device/apple2_controller.py +371 -0
  166. dodal/devices/insertion_device/apple2_undulator.py +184 -587
  167. dodal/devices/insertion_device/apple_knot_controller.py +222 -0
  168. dodal/devices/insertion_device/energy.py +161 -0
  169. dodal/devices/insertion_device/energy_motor_lookup.py +21 -28
  170. dodal/devices/insertion_device/lookup_table_models.py +47 -52
  171. dodal/devices/insertion_device/polarisation.py +36 -0
  172. dodal/devices/ipin.py +1 -1
  173. dodal/devices/linkam3.py +7 -5
  174. dodal/devices/motors.py +107 -19
  175. dodal/devices/mx_phase1/beamstop.py +2 -4
  176. dodal/devices/oav/oav_calculations.py +20 -13
  177. dodal/devices/oav/oav_detector.py +92 -22
  178. dodal/devices/oav/oav_parameters.py +4 -9
  179. dodal/devices/oav/oav_to_redis_forwarder.py +22 -18
  180. dodal/devices/oav/pin_image_recognition/__init__.py +4 -6
  181. dodal/devices/oav/pin_image_recognition/manual_test.py +1 -2
  182. dodal/devices/oav/pin_image_recognition/utils.py +30 -32
  183. dodal/devices/oav/snapshots/grid_overlay.py +10 -9
  184. dodal/devices/oav/snapshots/snapshot_image_processing.py +15 -13
  185. dodal/devices/oav/utils.py +20 -6
  186. dodal/devices/p45.py +3 -9
  187. dodal/devices/pgm.py +8 -14
  188. dodal/devices/pressure_jump_cell.py +93 -32
  189. dodal/devices/qbpm.py +1 -3
  190. dodal/devices/robot.py +45 -20
  191. dodal/devices/s4_slit_gaps.py +1 -1
  192. dodal/devices/selectable_source.py +41 -0
  193. dodal/devices/slits.py +2 -5
  194. dodal/devices/smargon.py +2 -3
  195. dodal/devices/temperture_controller/lakeshore/lakeshore.py +38 -64
  196. dodal/devices/temperture_controller/lakeshore/lakeshore_io.py +21 -35
  197. dodal/devices/tetramm.py +7 -7
  198. dodal/devices/turbo_slit.py +8 -7
  199. dodal/devices/undulator.py +42 -56
  200. dodal/devices/util/adjuster_plans.py +2 -3
  201. dodal/devices/util/epics_util.py +10 -10
  202. dodal/devices/util/lookup_tables.py +17 -18
  203. dodal/devices/v2f.py +2 -3
  204. dodal/devices/watsonmarlow323_pump.py +1 -1
  205. dodal/devices/xbpm_feedback.py +3 -2
  206. dodal/devices/xspress3/xspress3.py +8 -11
  207. dodal/devices/xspress3/xspress3_channel.py +3 -6
  208. dodal/devices/zebra/zebra.py +21 -7
  209. dodal/devices/zebra/zebra_constants_mapping.py +12 -7
  210. dodal/devices/zebra/zebra_controlled_shutter.py +2 -1
  211. dodal/devices/zocalo/zocalo_interaction.py +14 -14
  212. dodal/devices/zocalo/zocalo_results.py +33 -33
  213. dodal/log.py +23 -20
  214. dodal/plan_stubs/check_topup.py +15 -15
  215. dodal/plan_stubs/data_session.py +6 -6
  216. dodal/plan_stubs/motor_utils.py +22 -18
  217. dodal/plan_stubs/pressure_jump_cell.py +18 -0
  218. dodal/plan_stubs/wrapped.py +40 -55
  219. dodal/plans/bimorph.py +63 -52
  220. dodal/plans/configure_arm_trigger_and_disarm_detector.py +0 -1
  221. dodal/plans/device_setup_plans/__init__.py +5 -0
  222. dodal/plans/device_setup_plans/setup_pin_tip_params.py +63 -0
  223. dodal/plans/preprocessors/verify_undulator_gap.py +10 -8
  224. dodal/plans/spec_path.py +3 -5
  225. dodal/plans/verify_undulator_gap.py +1 -2
  226. dodal/plans/wrapped.py +4 -3
  227. dodal/testing/__init__.py +0 -0
  228. dodal/testing/electron_analyser/device_factory.py +5 -7
  229. dodal/testing/fixtures/devices/apple2.py +38 -0
  230. dodal/testing/fixtures/run_engine.py +3 -7
  231. dodal/testing/fixtures/utils.py +1 -2
  232. dodal/utils.py +60 -58
  233. dls_dodal-1.68.0.dist-info/RECORD +0 -330
  234. dodal/beamline_specific_utils/i05_shared.py +0 -14
  235. dodal/devices/b07/__init__.py +0 -3
  236. dodal/devices/i04/max_pixel.py +0 -38
  237. dodal/devices/i05/__init__.py +0 -3
  238. dodal/devices/i09/__init__.py +0 -3
  239. dodal/devices/i21/__init__.py +0 -5
  240. {dls_dodal-1.68.0.dist-info → dls_dodal-2.0.0.dist-info}/entry_points.txt +0 -0
  241. {dls_dodal-1.68.0.dist-info → dls_dodal-2.0.0.dist-info}/licenses/LICENSE +0 -0
  242. {dls_dodal-1.68.0.dist-info → dls_dodal-2.0.0.dist-info}/top_level.txt +0 -0
  243. /dodal/{beamline_specific_utils → devices/beamlines}/__init__.py +0 -0
  244. /dodal/devices/{b07 → beamlines/b07}/enums.py +0 -0
  245. /dodal/devices/{b07_1 → beamlines/b07_1}/enums.py +0 -0
  246. /dodal/devices/{b16 → beamlines/b16}/__init__.py +0 -0
  247. /dodal/devices/{i02_1 → beamlines/i02_1}/__init__.py +0 -0
  248. /dodal/devices/{i02_2 → beamlines/i02_2}/__init__.py +0 -0
  249. /dodal/devices/{i03 → beamlines/i03}/__init__.py +0 -0
  250. /dodal/devices/{i03 → beamlines/i03}/constants.py +0 -0
  251. /dodal/devices/{i04 → beamlines/i04}/__init__.py +0 -0
  252. /dodal/devices/{i04 → beamlines/i04}/constants.py +0 -0
  253. /dodal/devices/{i05 → beamlines/i05}/enums.py +0 -0
  254. /dodal/devices/{i07 → beamlines/i07}/__init__.py +0 -0
  255. /dodal/devices/{i09 → beamlines/i09}/enums.py +0 -0
  256. /dodal/devices/{i09_1 → beamlines/i09_1}/__init__.py +0 -0
  257. /dodal/devices/{i09_1 → beamlines/i09_1}/enums.py +0 -0
  258. /dodal/devices/{i09_1_shared → beamlines/i09_1_shared}/__init__.py +0 -0
  259. /dodal/devices/{i09_2_shared → beamlines/i09_2_shared}/__init__.py +0 -0
  260. /dodal/devices/{i09_2_shared → beamlines/i09_2_shared}/i09_apple2.py +0 -0
  261. /dodal/devices/{i10 → beamlines/i10}/__init__.py +0 -0
  262. /dodal/devices/{i10 → beamlines/i10}/i10_setting_data.py +0 -0
  263. /dodal/devices/{i10 → beamlines/i10}/mirrors.py +0 -0
  264. /dodal/devices/{i10 → beamlines/i10}/rasor/__init__.py +0 -0
  265. /dodal/devices/{i10 → beamlines/i10}/rasor/rasor_scaler_cards.py +0 -0
  266. /dodal/devices/{i11 → beamlines/i10_1/electromagnet}/__init__.py +0 -0
  267. /dodal/devices/{i13_1 → beamlines/i11}/__init__.py +0 -0
  268. /dodal/devices/{i15 → beamlines/i13_1}/__init__.py +0 -0
  269. /dodal/devices/{i13_1 → beamlines/i13_1}/merlin_controller.py +0 -0
  270. /dodal/devices/{i17 → beamlines/i15}/__init__.py +0 -0
  271. /dodal/devices/{i15 → beamlines/i15}/laue.py +0 -0
  272. /dodal/devices/{i15 → beamlines/i15}/motors.py +0 -0
  273. /dodal/devices/{i15 → beamlines/i15}/rail.py +0 -0
  274. /dodal/devices/{i18 → beamlines/i17}/__init__.py +0 -0
  275. /dodal/devices/{i19 → beamlines/i18}/__init__.py +0 -0
  276. /dodal/devices/{i18 → beamlines/i18}/kb_mirror.py +0 -0
  277. /dodal/devices/{i19/access_controlled → beamlines/i19}/__init__.py +0 -0
  278. /dodal/devices/{i20_1 → beamlines/i19/access_controlled}/__init__.py +0 -0
  279. /dodal/devices/{i19 → beamlines/i19}/access_controlled/hutch_access.py +0 -0
  280. /dodal/devices/{i19 → beamlines/i19}/beamstop.py +0 -0
  281. /dodal/devices/{i19 → beamlines/i19}/diffractometer.py +0 -0
  282. /dodal/devices/{i22 → beamlines/i20_1}/__init__.py +0 -0
  283. /dodal/devices/{i21 → beamlines/i21}/enums.py +0 -0
  284. /dodal/devices/{i24 → beamlines/i22}/__init__.py +0 -0
  285. /dodal/devices/{p99 → beamlines/i24}/__init__.py +0 -0
  286. /dodal/devices/{i24 → beamlines/i24}/aperture.py +0 -0
  287. /dodal/devices/{i24 → beamlines/i24}/focus_mirrors.py +0 -0
  288. /dodal/devices/{i24 → beamlines/i24}/vgonio.py +0 -0
  289. /dodal/devices/{p60 → beamlines/p60}/__init__.py +0 -0
  290. /dodal/devices/{p60 → beamlines/p60}/enums.py +0 -0
  291. /dodal/devices/{p99 → beamlines/p99}/sample_stage.py +0 -0
  292. /dodal/devices/insertion_device/{id_enum.py → enum.py} +0 -0
@@ -15,7 +15,7 @@ from .base_driver_io import (
15
15
  GenericAnalyserDriverIO,
16
16
  TAbstractAnalyserDriverIO,
17
17
  )
18
- from .base_enums import EnergyMode, SelectedSource
18
+ from .base_enums import EnergyMode
19
19
  from .base_region import (
20
20
  AbstractBaseRegion,
21
21
  AbstractBaseSequence,
@@ -27,7 +27,7 @@ from .base_region import (
27
27
  TLensMode,
28
28
  )
29
29
  from .base_util import to_binding_energy, to_kinetic_energy
30
- from .energy_sources import DualEnergySource, EnergySource
30
+ from .energy_sources import AbstractEnergySource, DualEnergySource, EnergySource
31
31
 
32
32
  __all__ = [
33
33
  "ElectronAnalyserController",
@@ -42,7 +42,6 @@ __all__ = [
42
42
  "GenericAnalyserDriverIO",
43
43
  "TAbstractAnalyserDriverIO",
44
44
  "EnergyMode",
45
- "SelectedSource",
46
45
  "AbstractBaseRegion",
47
46
  "AbstractBaseSequence",
48
47
  "GenericRegion",
@@ -53,6 +52,7 @@ __all__ = [
53
52
  "TLensMode",
54
53
  "to_binding_energy",
55
54
  "to_kinetic_energy",
55
+ "AbstractEnergySource",
56
56
  "DualEnergySource",
57
57
  "EnergySource",
58
58
  ]
@@ -12,46 +12,53 @@ from dodal.devices.electron_analyser.base.base_region import (
12
12
  GenericRegion,
13
13
  TAbstractBaseRegion,
14
14
  )
15
- from dodal.devices.electron_analyser.base.energy_sources import (
16
- AbstractEnergySource,
17
- DualEnergySource,
18
- )
15
+ from dodal.devices.electron_analyser.base.energy_sources import AbstractEnergySource
16
+ from dodal.devices.fast_shutter import FastShutter
17
+ from dodal.devices.selectable_source import SourceSelector
19
18
 
20
19
 
21
20
  class ElectronAnalyserController(
22
21
  ConstantDeadTimeController[TAbstractAnalyserDriverIO],
23
22
  Generic[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
24
23
  ):
25
- """
26
- Specialised controller for the electron analysers to provide additional setup logic
27
- such as selecting the energy source to use from requested region and giving the
28
- driver the correct region parameters.
24
+ """Specialised controller for the electron analysers to provide additional setup
25
+ logic such as selecting the energy source to use from requested region and giving
26
+ the driver the correct region parameters.
27
+
28
+ Args:
29
+ driver (TAbstractAnalyserDriverIO): The electron analyser driver to wrap
30
+ around that holds the PV's.
31
+ energy_source (AbstractEnergySource): Device that holds the excitation
32
+ energy and ability to switch between sources.
33
+ deadtime (float, optional): For a given exposure, what is the safest minimum
34
+ time between exposures that can be determined without reading signals.
35
+ image_mode (ADImageMode, optional): The image mode to configure the driver
36
+ with before measuring.
29
37
  """
30
38
 
31
39
  def __init__(
32
40
  self,
33
41
  driver: TAbstractAnalyserDriverIO,
34
42
  energy_source: AbstractEnergySource,
35
- deadtime: float,
43
+ shutter: FastShutter | None = None,
44
+ source_selector: SourceSelector | None = None,
45
+ deadtime: float = 0,
36
46
  image_mode: ADImageMode = ADImageMode.SINGLE,
37
47
  ):
38
- """
39
- Parameters:
40
- driver: The electron analyser driver to wrap around that holds the PV's.
41
- energy_source: Device that holds the excitation energy and ability to switch
42
- between sources.
43
- deadtime: For a given exposure, what is the safest minimum time between
44
- exposures that can be determined without reading signals.
45
- image_mode: The image mode to configure the driver with before measuring.
46
- """
47
48
  self.energy_source = energy_source
49
+ self.shutter = shutter
50
+ self.source_selector = source_selector
48
51
  super().__init__(driver, deadtime, image_mode)
49
52
 
50
- async def setup_with_region(self, region: TAbstractBaseRegion):
53
+ async def setup_with_region(self, region: TAbstractBaseRegion) -> None:
51
54
  """Logic to set the driver with a region."""
55
+ if self.source_selector is not None:
56
+ await self.source_selector.set(region.excitation_energy_source)
57
+
58
+ # Should this be moved to a VGScientController only?
59
+ if self.shutter is not None:
60
+ await self.shutter.set(self.shutter.close_state)
52
61
 
53
- if isinstance(self.energy_source, DualEnergySource):
54
- self.energy_source.selected_source.set(region.excitation_energy_source)
55
62
  excitation_energy = await self.energy_source.energy.get_value()
56
63
  epics_region = region.prepare_for_epics(excitation_energy)
57
64
  await self.driver.set(epics_region)
@@ -62,6 +69,10 @@ class ElectronAnalyserController(
62
69
  # axis calculation.
63
70
  excitation_energy = await self.energy_source.energy.get_value()
64
71
  await self.driver.cached_excitation_energy.set(excitation_energy)
72
+
73
+ if self.shutter is not None:
74
+ await self.shutter.set(self.shutter.open_state)
75
+
65
76
  await super().prepare(trigger_info)
66
77
 
67
78
 
@@ -33,9 +33,8 @@ class BaseElectronAnalyserDetector(
33
33
  AsyncConfigurable,
34
34
  Generic[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
35
35
  ):
36
- """
37
- Detector for data acquisition of electron analyser. Can only acquire using settings
38
- already configured for the device.
36
+ """Detector for data acquisition of electron analyser. Can only acquire using
37
+ settings already configured for the device.
39
38
 
40
39
  If possible, this should be changed to inherit from a StandardDetector. Currently,
41
40
  StandardDetector forces you to use a file writer which doesn't apply here.
@@ -90,9 +89,8 @@ class ElectronAnalyserRegionDetector(
90
89
  BaseElectronAnalyserDetector[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
91
90
  Generic[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
92
91
  ):
93
- """
94
- Extends electron analyser detector to configure specific region settings before data
95
- acquisition. It is designed to only exist inside a plan.
92
+ """Extends electron analyser detector to configure specific region settings before
93
+ data acquisition. It is designed to only exist inside a plan.
96
94
  """
97
95
 
98
96
  def __init__(
@@ -127,10 +125,9 @@ class ElectronAnalyserDetector(
127
125
  Stageable,
128
126
  Generic[TAbstractBaseSequence, TAbstractAnalyserDriverIO, TAbstractBaseRegion],
129
127
  ):
130
- """
131
- Electron analyser detector with the additional functionality to load a sequence file
132
- and create a list of temporary ElectronAnalyserRegionDetector objects. These will
133
- setup configured region settings before data acquisition.
128
+ """Electron analyser detector with the additional functionality to load a sequence
129
+ file and create a list of temporary ElectronAnalyserRegionDetector objects. These
130
+ will setup configured region settings before data acquisition.
134
131
  """
135
132
 
136
133
  def __init__(
@@ -146,8 +143,7 @@ class ElectronAnalyserDetector(
146
143
 
147
144
  @AsyncStatus.wrap
148
145
  async def stage(self) -> None:
149
- """
150
- Prepare the detector for use by ensuring it is idle and ready.
146
+ """Prepare the detector for use by ensuring it is idle and ready.
151
147
 
152
148
  This method asynchronously stages the detector by first disarming the controller
153
149
  to ensure the detector is not actively acquiring data, then invokes the driver's
@@ -165,11 +161,10 @@ class ElectronAnalyserDetector(
165
161
  await self._controller.disarm()
166
162
 
167
163
  def load_sequence(self, filename: str) -> TAbstractBaseSequence:
168
- """
169
- Load the sequence data from a provided json file into a sequence class.
164
+ """Load the sequence data from a provided json file into a sequence class.
170
165
 
171
166
  Args:
172
- filename: Path to the sequence file containing the region data.
167
+ filename (str): Path to the sequence file containing the region data.
173
168
 
174
169
  Returns:
175
170
  Pydantic model representing the sequence file.
@@ -181,17 +176,17 @@ class ElectronAnalyserDetector(
181
176
  ) -> list[
182
177
  ElectronAnalyserRegionDetector[TAbstractAnalyserDriverIO, TAbstractBaseRegion]
183
178
  ]:
184
- """
185
- Create a list of detectors equal to the number of regions in a sequence file.
179
+ """Create a list of detectors equal to the number of regions in a sequence file.
186
180
  Each detector is responsible for setting up a specific region.
187
181
 
188
182
  Args:
189
- filename: Path to the sequence file containing the region data.
190
- enabled_only: If true, only include the region if enabled is True.
183
+ filename (str): Path to the sequence file containing the region data.
184
+ enabled_only (bool, optional): If true, only include the region if enabled
185
+ is True.
191
186
 
192
187
  Returns:
193
188
  List of ElectronAnalyserRegionDetector, equal to the number of regions in
194
- the sequence file.
189
+ the sequence file.
195
190
  """
196
191
  seq = self.load_sequence(filename)
197
192
  regions: list[TAbstractBaseRegion] = (
@@ -41,10 +41,24 @@ class AbstractAnalyserDriverIO(
41
41
  Movable[TAbstractBaseRegion],
42
42
  Generic[TAbstractBaseRegion, TAcquisitionMode, TLensMode, TPsuMode, TPassEnergy],
43
43
  ):
44
- """
45
- Driver device that defines signals and readables that should be common to all
44
+ """Driver device that defines signals and readables that should be common to all
46
45
  electron analysers. Implementations of electron analyser devices should inherit
47
46
  from this class and define additional specialised signals and methods.
47
+
48
+ Args:
49
+ prefix (str): Base PV to connect to EPICS for this device.
50
+ acquisition_mode_type (type[TAcquisitionMode]): Enum that determines the
51
+ available acquisition modes for this device.
52
+ lens_mode_type (type[TLensMode]): Enum that determines the available lens
53
+ mode for this device.
54
+ psu_mode_type (type[TPsuMode]): Enum that determines the available psu modes
55
+ for this device.
56
+ pass_energy_type (type[TPassEnergy]): Can be enum or float, depending on
57
+ electron analyser model. If enum, it determines the available pass
58
+ energies for this device.
59
+ energy_source: Device that can give us the correct excitation energy (in eV)
60
+ and switch sources if applicable.
61
+ name (str, optional): Name of the device.
48
62
  """
49
63
 
50
64
  def __init__(
@@ -56,24 +70,6 @@ class AbstractAnalyserDriverIO(
56
70
  pass_energy_type: type[TPassEnergy],
57
71
  name: str = "",
58
72
  ) -> None:
59
- """
60
- Constructor method for setting up electron analyser.
61
-
62
- Args:
63
- prefix: Base PV to connect to EPICS for this device.
64
- acquisition_mode_type: Enum that determines the available acquisition modes
65
- for this device.
66
- lens_mode_type: Enum that determines the available lens mode for this
67
- device.
68
- psu_mode_type: Enum that determines the available psu modes for this device.
69
- pass_energy_type: Can be enum or float, depends on electron analyser model.
70
- If enum, it determines the available pass energies for
71
- this device.
72
- energy_source: Device that can give us the correct excitation energy and
73
- switch sources if applicable.
74
- (in eV).
75
- name: Name of the device.
76
- """
77
73
  self.acquisition_mode_type = acquisition_mode_type
78
74
  self.lens_mode_type = lens_mode_type
79
75
  self.psu_mode_type = psu_mode_type
@@ -140,33 +136,31 @@ class AbstractAnalyserDriverIO(
140
136
  @abstractmethod
141
137
  @AsyncStatus.wrap
142
138
  async def set(self, epics_region: TAbstractBaseRegion):
143
- """
144
- Move a group of signals defined in a region. Each implementation of this class
145
- is responsible for implementing this method correctly.
139
+ """Move a group of signals defined in a region. Each implementation of this
140
+ class is responsible for implementing this method correctly.
146
141
 
147
142
  Args:
148
- region: Contains the parameters to setup the driver for a scan.
143
+ epics_region (TAbstractBaseRegion): Contains the parameters to setup the
144
+ driver for a scan.
149
145
  """
150
146
 
151
147
  @abstractmethod
152
148
  def _create_angle_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]:
153
- """
154
- The signal that defines the angle axis. Depends on analyser model.
149
+ """The signal that defines the angle axis. Depends on analyser model.
155
150
 
156
151
  Args:
157
- prefix: PV string used for connecting to angle axis.
152
+ prefix (str): PV string used for connecting to angle axis.
158
153
 
159
154
  Returns:
160
- Signal that can give us angle axis array data.
155
+ SignalR that can give us angle axis array data.
161
156
  """
162
157
 
163
158
  @abstractmethod
164
159
  def _create_energy_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]:
165
- """
166
- The signal that defines the energy axis. Depends on analyser model.
160
+ """The signal that defines the energy axis. Depends on analyser model.
167
161
 
168
162
  Args:
169
- prefix: PV string used for connecting to energy axis.
163
+ prefix (str): PV string used for connecting to energy axis.
170
164
 
171
165
  Returns:
172
166
  Signal that can give us energy axis array data.
@@ -178,16 +172,16 @@ class AbstractAnalyserDriverIO(
178
172
  excitation_energy: float,
179
173
  energy_mode: EnergyMode,
180
174
  ) -> Array1D[np.float64]:
181
- """
182
- Calculate the binding energy axis to calibrate the spectra data. Function for a
183
- derived signal.
175
+ """Calculate the binding energy axis to calibrate the spectra data. Function for
176
+ a derived signal.
184
177
 
185
178
  Args:
186
- energy_axis: Array data of the original energy_axis from epics.
187
- excitation_energy: The excitation energy value used for the scan of this
188
- region.
189
- energy_mode: The energy_mode of the region that was used for the scan
190
- of this region.
179
+ energy_axis (Array1D[np.float64]): Array data of the original energy_axis
180
+ from epics.
181
+ excitation_energy (float): The excitation energy value used for the scan of
182
+ this region.
183
+ energy_mode (EnergyMode): The energy_mode of the region that was used for
184
+ the scan of this region.
191
185
 
192
186
  Returns:
193
187
  Array that is the correct axis for the spectra data.
@@ -205,17 +199,16 @@ class AbstractAnalyserDriverIO(
205
199
  def _calculate_total_time(
206
200
  self, total_steps: int, step_time: float, iterations: int
207
201
  ) -> float:
208
- """
209
- Calulcate the total time the scan takes for this region. Function for a derived
210
- signal.
202
+ """Calulcate the total time the scan takes for this region. Function for a
203
+ derived signal.
211
204
 
212
205
  Args:
213
- total_steps: Number of steps for the region.
214
- step_time: Time for each step for the region.
215
- iterations: The number of iterations the region collected data for.
206
+ total_steps (int): Number of steps for the region.
207
+ step_time (float): Time for each step for the region.
208
+ iterations (int): The number of iterations the region collected data for.
216
209
 
217
210
  Returns:
218
- Calculated total time in seconds.
211
+ Float: Calculated total time in seconds.
219
212
  """
220
213
  return total_steps * step_time * iterations
221
214
 
@@ -4,8 +4,3 @@ from ophyd_async.core import StrictEnum
4
4
  class EnergyMode(StrictEnum):
5
5
  KINETIC = "Kinetic"
6
6
  BINDING = "Binding"
7
-
8
-
9
- class SelectedSource(StrictEnum):
10
- SOURCE1 = "source1"
11
- SOURCE2 = "source2"
@@ -6,11 +6,12 @@ from typing import Generic, Self, TypeAlias, TypeVar
6
6
  from ophyd_async.core import StrictEnum, SupersetEnum
7
7
  from pydantic import BaseModel, Field, model_validator
8
8
 
9
- from dodal.devices.electron_analyser.base.base_enums import EnergyMode, SelectedSource
9
+ from dodal.devices.electron_analyser.base.base_enums import EnergyMode
10
10
  from dodal.devices.electron_analyser.base.base_util import (
11
11
  to_binding_energy,
12
12
  to_kinetic_energy,
13
13
  )
14
+ from dodal.devices.selectable_source import SelectedSource
14
15
 
15
16
  AnyAcqMode: TypeAlias = StrictEnum
16
17
  AnyLensMode: TypeAlias = SupersetEnum | StrictEnum
@@ -27,11 +28,13 @@ TPsuMode = TypeVar("TPsuMode", bound=AnyPsuMode)
27
28
 
28
29
 
29
30
  def java_to_python_case(java_str: str) -> str:
30
- """
31
- Convert a camelCase Java-style string to a snake_case Python-style string.
31
+ """Convert a camelCase Java-style string to a snake_case Python-style string.
32
+
33
+ Args:
34
+ java_str (str): The Java-style camelCase string.
32
35
 
33
- :param java_str: The Java-style camelCase string.
34
- :return: The Python-style snake_case string.
36
+ Returns:
37
+ str: The Python-style snake_case string.
35
38
  """
36
39
  new_value = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", java_str)
37
40
  new_value = re.sub("([a-z0-9])([A-Z])", r"\1_\2", new_value).lower()
@@ -66,9 +69,8 @@ class AbstractBaseRegion(
66
69
  JavaToPythonModel,
67
70
  Generic[TAcquisitionMode, TLensMode, TPassEnergy],
68
71
  ):
69
- """
70
- Generic region model that holds the data. Specialised region models should inherit
71
- this to extend functionality. All energy units are assumed to be in eV.
72
+ """Generic region model that holds the data. Specialised region models should
73
+ inherit this to extend functionality. All energy units are assumed to be in eV.
72
74
  """
73
75
 
74
76
  name: str = "New_region"
@@ -88,15 +90,11 @@ class AbstractBaseRegion(
88
90
  energy_mode: EnergyMode = EnergyMode.KINETIC
89
91
 
90
92
  def is_binding_energy(self) -> bool:
91
- """
92
- Returns true if the energy_mode is binding.
93
- """
93
+ """Returns true if the energy_mode is binding."""
94
94
  return self.energy_mode == EnergyMode.BINDING
95
95
 
96
96
  def is_kinetic_energy(self) -> bool:
97
- """
98
- Returns true if the energy_mode is kinetic.
99
- """
97
+ """Returns true if the energy_mode is kinetic."""
100
98
  return self.energy_mode == EnergyMode.KINETIC
101
99
 
102
100
  def switch_energy_mode(
@@ -105,19 +103,18 @@ class AbstractBaseRegion(
105
103
  excitation_energy: float,
106
104
  copy: bool = True,
107
105
  ) -> Self:
108
- """
109
- Get a region with a new energy mode: Kinetic or Binding.
106
+ """Get a region with a new energy mode: Kinetic or Binding.
110
107
  It caculates new values for low_energy, centre_energy, high_energy, via the
111
108
  excitation enerrgy. It doesn't calculate anything if the region is already of
112
109
  the same energy mode.
113
110
 
114
- Parameters:
115
- energy_mode: Mode you want to switch the region to.
116
- excitation_energy: Energy conversion for low_energy, centre_energy, and
117
- high_energy for new energy mode.
118
- copy: Defaults to True. If true, create a copy of this region to alter for
119
- the new energy_mode and return it. If False, alter this region for the
120
- energy_mode and return it self.
111
+ Args:
112
+ energy_mode (EnergyMode): Mode you want to switch the region to.
113
+ excitation_energy (float): Energy conversion for low_energy, centre_energy,
114
+ and high_energy for new energy mode.
115
+ copy (bool, optional): Defaults to True. If true, create a copy of this
116
+ region to alter for the new energy_mode and return it. If False, alter
117
+ this region for the energy_mode and return it self.
121
118
 
122
119
  Returns:
123
120
  Region with selected energy mode and new calculated energy values.
@@ -146,14 +143,16 @@ class AbstractBaseRegion(
146
143
  new values for low_energy, centre_energy, and high_energy while also preserving
147
144
  the original energy mode e.g mode BINDING will stay as BINDING.
148
145
 
149
- Parameters:
150
- excitation_energy: Energy conversion for low_energy, centre_energy, and
151
- high_energy for new energy mode.
152
- copy: Defaults to True. If true, create a copy of this region to alter to
153
- calculate new energy values to return. If false, alter this region.
146
+ Args:
147
+ excitation_energy (float): Energy conversion for low_energy, centre_energy,
148
+ and high_energy for new energy mode.
149
+ copy (bool, optional): Defaults to True. If true, create a copy of this
150
+ region to alter to calculate new energy values to return. If false,
151
+ alter this region.
152
+
154
153
  Returns:
155
154
  Region with selected original energy mode and new calculated KINETIC energy
156
- values for epics.
155
+ values for epics.
157
156
  """
158
157
  original_energy_mode = self.energy_mode
159
158
  r = self.switch_energy_mode(EnergyMode.KINETIC, excitation_energy, copy)
@@ -176,8 +175,7 @@ class AbstractBaseSequence(
176
175
  JavaToPythonModel,
177
176
  Generic[TAbstractBaseRegion],
178
177
  ):
179
- """
180
- Generic sequence model that holds the list of region data. Specialised sequence
178
+ """Generic sequence model that holds the list of region data. Specialised sequence
181
179
  models should inherit this to extend functionality and define type of region to
182
180
  hold.
183
181
  """
@@ -4,15 +4,16 @@ from dodal.devices.electron_analyser.base.base_enums import EnergyMode
4
4
  def to_kinetic_energy(
5
5
  value: float, value_mode: EnergyMode, excitation_energy: float
6
6
  ) -> float:
7
- """
8
- Convert a value that is binding energy to kinetic energy.
9
- Parameters:
10
- value: The value to convert.
11
- value_mode: Energy mode of the value. If it is already kinetic, return the
12
- same value. If it is binding, convert to kinetic.
13
- excitation_energy: Value to calculate the conversion.
7
+ """Convert a value that is binding energy to kinetic energy.
8
+
9
+ Args:
10
+ value (float): The value to convert.
11
+ value_mode (EnergyMode): Energy mode of the value. If it is already kinetic,
12
+ return the same value. If it is binding, convert to kinetic.
13
+ excitation_energy (float): Value to calculate the conversion.
14
+
14
15
  Returns:
15
- Caluclated kinetic energy value
16
+ float: Caluclated kinetic energy value.
16
17
  """
17
18
  return value if value_mode == EnergyMode.KINETIC else excitation_energy - value
18
19
 
@@ -20,14 +21,15 @@ def to_kinetic_energy(
20
21
  def to_binding_energy(
21
22
  value: float, value_mode: EnergyMode, excitation_energy: float
22
23
  ) -> float:
23
- """
24
- Convert a value that is kinetic energy to binding energy.
25
- Parameters:
26
- value: The value to convert.
27
- value_mode: Energy mode of the value. If it is already binding, return the
28
- same value. If it is kinetic, convert to binding.
29
- excitation_energy: Value to calculate the conversion.
24
+ """Convert a value that is kinetic energy to binding energy.
25
+
26
+ Args:
27
+ value (float): The value to convert.
28
+ value_mode (EnergyMode): Energy mode of the value. If it is already binding,
29
+ return the same value. If it is kinetic, convert to binding.
30
+ excitation_energy (float): Value to calculate the conversion.
31
+
30
32
  Returns:
31
- Caluclated binding energy value
33
+ float: Caluclated binding energy value.
32
34
  """
33
35
  return value if value_mode == EnergyMode.BINDING else excitation_energy - value
@@ -3,20 +3,19 @@ from abc import abstractmethod
3
3
  from ophyd_async.core import (
4
4
  Reference,
5
5
  SignalR,
6
+ SignalRW,
6
7
  StandardReadable,
7
8
  StandardReadableFormat,
8
9
  derived_signal_r,
9
10
  soft_signal_r_and_setter,
10
- soft_signal_rw,
11
11
  )
12
12
 
13
- from dodal.devices.electron_analyser.base.base_enums import SelectedSource
13
+ from dodal.devices.selectable_source import SelectedSource, get_obj_from_selected_source
14
14
 
15
15
 
16
16
  class AbstractEnergySource(StandardReadable):
17
- """
18
- Abstract device that wraps an energy source signal and provides common interface via
19
- a energy signal.
17
+ """Abstract device that wraps an energy source signal and provides common interface
18
+ via a energy signal.
20
19
  """
21
20
 
22
21
  def __init__(self, name: str = "") -> None:
@@ -25,14 +24,11 @@ class AbstractEnergySource(StandardReadable):
25
24
  @property
26
25
  @abstractmethod
27
26
  def energy(self) -> SignalR[float]:
28
- """
29
- Signal to provide the excitation energy value in eV.
30
- """
27
+ """Signal to provide the excitation energy value in eV."""
31
28
 
32
29
 
33
30
  class EnergySource(AbstractEnergySource):
34
- """
35
- Wraps a signal that relates to energy and provides common interface via energy
31
+ """Wraps a signal that relates to energy and provides common interface via energy
36
32
  signal. It provides the name of the wrapped signal as a child signal in the
37
33
  read_configuration via wrapped_device_name and adds the signal as a readable.
38
34
  """
@@ -51,51 +47,50 @@ class EnergySource(AbstractEnergySource):
51
47
  return self._source_ref()
52
48
 
53
49
 
50
+ def get_float_from_selected_source(
51
+ selected: SelectedSource, s1: float, s2: float
52
+ ) -> float:
53
+ """Wrapper function to provide type hints for derived signal."""
54
+ return get_obj_from_selected_source(selected, s1, s2)
55
+
56
+
54
57
  class DualEnergySource(AbstractEnergySource):
55
- """
56
- Holds two EnergySource devices and provides a signal to read energy depending on
57
- which source is selected. This is controlled by a selected_source signal which can
58
- switch source using SelectedSource enum. Both sources energy is recorded in the
59
- read, the energy signal is used as a helper signal to know which source is being
60
- used.
58
+ """Holds two EnergySource devices and provides a signal to read energy depending on
59
+ which source is selected. The energy is the one that corrosponds to the
60
+ selected_source signal. For example, selected_source is source1 if selected_source
61
+ is at SelectedSource.SOURCE1 and vise versa for source2 and
62
+ SelectedSource.SOURCE2.
63
+
64
+ Args:
65
+ source1 (SignalR): Energy source that corrosponds to SelectedSource.SOURCE1.
66
+ source2 (SignalR): Energy source that corrosponds to SelectedSource.SOURCE2.
67
+ selected_source (SignalRW): Signal that decides the active energy source.
68
+ name (str, optional): Name of this device.
61
69
  """
62
70
 
63
71
  def __init__(
64
- self, source1: SignalR[float], source2: SignalR[float], name: str = ""
72
+ self,
73
+ source1: SignalR[float],
74
+ source2: SignalR[float],
75
+ selected_source: SignalRW[SelectedSource],
76
+ name: str = "",
65
77
  ):
66
- """
67
- Args:
68
- source1: Default energy signal to select.
69
- source2: Secondary energy signal to select.
70
- name: name of this device.
71
- """
72
-
78
+ self.selected_source_ref = Reference(selected_source)
73
79
  with self.add_children_as_readables():
74
- self.selected_source = soft_signal_rw(
75
- SelectedSource, initial_value=SelectedSource.SOURCE1
76
- )
77
80
  self.source1 = EnergySource(source1)
78
81
  self.source2 = EnergySource(source2)
79
82
 
80
83
  self._selected_energy = derived_signal_r(
81
- self._get_excitation_energy,
84
+ get_float_from_selected_source,
82
85
  "eV",
83
- selected_source=self.selected_source,
84
- source1=self.source1.energy,
85
- source2=self.source2.energy,
86
+ selected=self.selected_source_ref(),
87
+ s1=self.source1.energy,
88
+ s2=self.source2.energy,
86
89
  )
90
+ self.add_readables([selected_source])
87
91
 
88
92
  super().__init__(name)
89
93
 
90
- def _get_excitation_energy(
91
- self, selected_source: SelectedSource, source1: float, source2: float
92
- ) -> float:
93
- match selected_source:
94
- case SelectedSource.SOURCE1:
95
- return source1
96
- case SelectedSource.SOURCE2:
97
- return source2
98
-
99
94
  @property
100
95
  def energy(self) -> SignalR[float]:
101
96
  return self._selected_energy