dls-dodal 1.58.0__py3-none-any.whl → 1.60.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 (71) hide show
  1. {dls_dodal-1.58.0.dist-info → dls_dodal-1.60.0.dist-info}/METADATA +3 -3
  2. {dls_dodal-1.58.0.dist-info → dls_dodal-1.60.0.dist-info}/RECORD +71 -47
  3. dodal/_version.py +2 -2
  4. dodal/beamlines/__init__.py +1 -0
  5. dodal/beamlines/b07.py +10 -5
  6. dodal/beamlines/b07_1.py +10 -5
  7. dodal/beamlines/b21.py +22 -0
  8. dodal/beamlines/i02_1.py +80 -0
  9. dodal/beamlines/i03.py +5 -3
  10. dodal/beamlines/i04.py +5 -3
  11. dodal/beamlines/i09.py +10 -9
  12. dodal/beamlines/i09_1.py +10 -5
  13. dodal/beamlines/i10-1.py +25 -0
  14. dodal/beamlines/i10.py +17 -1
  15. dodal/beamlines/i11.py +0 -17
  16. dodal/beamlines/i15.py +242 -0
  17. dodal/beamlines/i15_1.py +156 -0
  18. dodal/beamlines/i19_1.py +3 -1
  19. dodal/beamlines/i19_2.py +12 -1
  20. dodal/beamlines/i21.py +27 -0
  21. dodal/beamlines/i22.py +12 -2
  22. dodal/beamlines/i24.py +32 -3
  23. dodal/beamlines/k07.py +31 -0
  24. dodal/beamlines/p60.py +10 -9
  25. dodal/common/watcher_utils.py +1 -1
  26. dodal/devices/apple2_undulator.py +18 -142
  27. dodal/devices/attenuator/attenuator.py +48 -2
  28. dodal/devices/attenuator/filter.py +3 -0
  29. dodal/devices/attenuator/filter_selections.py +26 -0
  30. dodal/devices/eiger.py +2 -1
  31. dodal/devices/electron_analyser/__init__.py +4 -0
  32. dodal/devices/electron_analyser/abstract/base_driver_io.py +30 -18
  33. dodal/devices/electron_analyser/energy_sources.py +101 -0
  34. dodal/devices/electron_analyser/specs/detector.py +6 -6
  35. dodal/devices/electron_analyser/specs/driver_io.py +7 -15
  36. dodal/devices/electron_analyser/vgscienta/detector.py +6 -6
  37. dodal/devices/electron_analyser/vgscienta/driver_io.py +7 -14
  38. dodal/devices/fast_grid_scan.py +130 -64
  39. dodal/devices/focusing_mirror.py +30 -0
  40. dodal/devices/i02_1/__init__.py +0 -0
  41. dodal/devices/i02_1/fast_grid_scan.py +61 -0
  42. dodal/devices/i02_1/sample_motors.py +19 -0
  43. dodal/devices/i04/murko_results.py +69 -23
  44. dodal/devices/i10/i10_apple2.py +282 -140
  45. dodal/devices/i15/dcm.py +77 -0
  46. dodal/devices/i15/focussing_mirror.py +71 -0
  47. dodal/devices/i15/jack.py +39 -0
  48. dodal/devices/i15/laue.py +18 -0
  49. dodal/devices/i15/motors.py +27 -0
  50. dodal/devices/i15/multilayer_mirror.py +25 -0
  51. dodal/devices/i15/rail.py +17 -0
  52. dodal/devices/i21/__init__.py +3 -0
  53. dodal/devices/i21/enums.py +8 -0
  54. dodal/devices/i22/nxsas.py +2 -0
  55. dodal/devices/i24/commissioning_jungfrau.py +114 -0
  56. dodal/devices/motors.py +52 -1
  57. dodal/devices/slits.py +18 -0
  58. dodal/devices/smargon.py +0 -56
  59. dodal/devices/temperture_controller/__init__.py +3 -0
  60. dodal/devices/temperture_controller/lakeshore/__init__.py +0 -0
  61. dodal/devices/temperture_controller/lakeshore/lakeshore.py +204 -0
  62. dodal/devices/temperture_controller/lakeshore/lakeshore_io.py +112 -0
  63. dodal/devices/tetramm.py +38 -16
  64. dodal/devices/v2f.py +39 -0
  65. dodal/devices/zebra/zebra.py +1 -0
  66. dodal/devices/zebra/zebra_constants_mapping.py +1 -1
  67. dodal/parameters/experiment_parameter_base.py +1 -5
  68. {dls_dodal-1.58.0.dist-info → dls_dodal-1.60.0.dist-info}/WHEEL +0 -0
  69. {dls_dodal-1.58.0.dist-info → dls_dodal-1.60.0.dist-info}/entry_points.txt +0 -0
  70. {dls_dodal-1.58.0.dist-info → dls_dodal-1.60.0.dist-info}/licenses/LICENSE +0 -0
  71. {dls_dodal-1.58.0.dist-info → dls_dodal-1.60.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,112 @@
1
+ from ophyd_async.core import Device, DeviceVector, SignalDatatypeT
2
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
3
+
4
+
5
+ class LakeshoreControlChannel(Device):
6
+ """
7
+ Single control channel for a Lakeshore temperature controller.
8
+
9
+ Provides access to setpoint, ramp rate, ramp enable, heater output, heater output range,
10
+ PID parameters (P, I, D), and manual output for the channel.
11
+ """
12
+
13
+ def __init__(
14
+ self,
15
+ prefix: str,
16
+ suffix: str,
17
+ heater_type: type[SignalDatatypeT],
18
+ name: str = "",
19
+ ):
20
+ """Initialize the LakeshoreControlChannel device.
21
+ Parameters
22
+ ----------
23
+ prefix: str
24
+ The EPICS prefix for the Lakeshore device.
25
+ suffix: str
26
+ Suffix for the channel, used to differentiate multiple channels.
27
+ heater_type: SignalDatatypeT
28
+ Type of the heater output range.
29
+ name: str
30
+ Optional name for the device.
31
+ """
32
+
33
+ def channel_rw(channel_type, pv_name):
34
+ return epics_signal_rw(
35
+ channel_type,
36
+ f"{prefix}{pv_name}{suffix}",
37
+ f"{prefix}{pv_name}_S{suffix}",
38
+ )
39
+
40
+ self.user_setpoint = channel_rw(channel_type=float, pv_name="SETP")
41
+ self.ramp_rate = channel_rw(channel_type=float, pv_name="RAMP")
42
+ self.ramp_enable = channel_rw(channel_type=int, pv_name="RAMPST")
43
+ self.heater_output_range = channel_rw(channel_type=heater_type, pv_name="RANGE")
44
+ self.p = channel_rw(channel_type=float, pv_name="P")
45
+ self.i = channel_rw(channel_type=float, pv_name="I")
46
+ self.d = channel_rw(channel_type=float, pv_name="D")
47
+ self.manual_output = channel_rw(channel_type=float, pv_name="MOUT")
48
+ self.heater_output = epics_signal_r(float, f"{prefix}{'HTR'}{suffix}")
49
+
50
+ super().__init__(name=name)
51
+
52
+
53
+ class LakeshoreBaseIO(Device):
54
+ """Base class for Lakeshore temperature controller IO.
55
+
56
+ Provides access to control channels and readback channels for setpoint, ramp rate, heater output,
57
+ and PID parameters. Supports both single and multiple control channel configurations.
58
+ Note:
59
+ Almost all models have a controller for each readback channel but some models
60
+ only has a single controller for multiple readback channels.
61
+ """
62
+
63
+ def __init__(
64
+ self,
65
+ prefix: str,
66
+ num_readback_channel: int,
67
+ heater_setting: type[SignalDatatypeT],
68
+ name: str = "",
69
+ single_control_channel: bool = False,
70
+ ):
71
+ """Initialize the LakeshoreBaseIO device.
72
+
73
+ Parameters
74
+ -----------
75
+ prefix: str
76
+ The EPICS prefix for the Lakeshore device.
77
+ num_readback_channel: int
78
+ Number of readback channels to create.
79
+ heater_setting: SignalDatatypeT
80
+ Type of the heater setting.
81
+ name: str
82
+ Optional name for the device.
83
+ single_control_channel: bool
84
+ If True, use a single control channel for all readback.
85
+ """
86
+
87
+ suffixes = (
88
+ [""]
89
+ if single_control_channel
90
+ else map(str, range(1, num_readback_channel + 1))
91
+ )
92
+ self.control_channels = DeviceVector(
93
+ {
94
+ i: LakeshoreControlChannel(
95
+ prefix=prefix, suffix=suffix, heater_type=heater_setting
96
+ )
97
+ for i, suffix in enumerate(suffixes, start=1)
98
+ }
99
+ )
100
+
101
+ self.readback = DeviceVector(
102
+ {
103
+ i: epics_signal_r(
104
+ float,
105
+ read_pv=f"{prefix}KRDG{i - 1}",
106
+ )
107
+ for i in range(1, num_readback_channel + 1)
108
+ }
109
+ )
110
+ super().__init__(
111
+ name=name,
112
+ )
dodal/devices/tetramm.py CHANGED
@@ -16,6 +16,7 @@ from ophyd_async.core import (
16
16
  TriggerInfo,
17
17
  set_and_wait_for_value,
18
18
  soft_signal_r_and_setter,
19
+ wait_for_value,
19
20
  )
20
21
  from ophyd_async.epics.adcore import (
21
22
  ADHDFWriter,
@@ -23,7 +24,7 @@ from ophyd_async.epics.adcore import (
23
24
  NDFileHDFIO,
24
25
  NDPluginBaseIO,
25
26
  )
26
- from ophyd_async.epics.core import PvSuffix, stop_busy_record
27
+ from ophyd_async.epics.core import PvSuffix, epics_signal_r
27
28
 
28
29
 
29
30
  class TetrammRange(StrictEnum):
@@ -77,6 +78,7 @@ class TetrammController(DetectorController):
77
78
  _supported_trigger_types = {
78
79
  DetectorTrigger.EDGE_TRIGGER: TetrammTrigger.EXT_TRIGGER,
79
80
  DetectorTrigger.CONSTANT_GATE: TetrammTrigger.EXT_TRIGGER,
81
+ DetectorTrigger.VARIABLE_GATE: TetrammTrigger.EXT_TRIGGER,
80
82
  }
81
83
  """"On the TetrAMM ASCII mode requires a minimum value of ValuesPerRead of 500,
82
84
  [...] binary mode the minimum value of ValuesPerRead is 5."
@@ -86,11 +88,9 @@ class TetrammController(DetectorController):
86
88
  """The TetrAMM always digitizes at 100 kHz"""
87
89
  _base_sample_rate: int = 100_000
88
90
 
89
- def __init__(
90
- self,
91
- driver: TetrammDriver,
92
- ) -> None:
91
+ def __init__(self, driver: TetrammDriver, file_io: NDFileHDFIO) -> None:
93
92
  self.driver = driver
93
+ self._file_io = file_io
94
94
  self._arm_status: AsyncStatus | None = None
95
95
 
96
96
  def get_deadtime(self, exposure: float | None) -> float:
@@ -107,13 +107,19 @@ class TetrammController(DetectorController):
107
107
  if trigger_info.livetime is None:
108
108
  raise ValueError(f"{self.__class__.__name__} requires that livetime is set")
109
109
 
110
+ current_trig_status = await self.driver.trigger_mode.get_value()
111
+
112
+ if current_trig_status == TetrammTrigger.FREE_RUN: # if freerun turn off first
113
+ await self.disarm()
114
+
110
115
  # trigger mode must be set first and on its own!
111
116
  await self.driver.trigger_mode.set(
112
117
  self._supported_trigger_types[trigger_info.trigger]
113
118
  )
119
+
114
120
  await asyncio.gather(
115
- self.driver.averaging_time.set(trigger_info.livetime),
116
121
  self.set_exposure(trigger_info.livetime),
122
+ self._file_io.num_capture.set(trigger_info.total_number_of_exposures),
117
123
  )
118
124
 
119
125
  # raise an error if asked to trigger faster than the max.
@@ -133,14 +139,18 @@ class TetrammController(DetectorController):
133
139
  self._arm_status = await self.start_acquiring_driver_and_ensure_status()
134
140
 
135
141
  async def wait_for_idle(self):
136
- if self._arm_status and not self._arm_status.done:
137
- await self._arm_status
138
- self._arm_status = None
142
+ # tetramm never goes idle really, actually it is always acquiring
143
+ # so need to wait for the capture to finish instead
144
+ await wait_for_value(self._file_io.acquire, False, timeout=None)
145
+
146
+ async def unstage(self):
147
+ await self.disarm()
148
+ await self._file_io.acquire.set(False)
139
149
 
140
150
  async def disarm(self):
141
151
  # We can't use caput callback as we already used it in arm() and we can't have
142
152
  # 2 or they will deadlock
143
- await stop_busy_record(self.driver.acquire, False, timeout=1)
153
+ await set_and_wait_for_value(self.driver.acquire, False, timeout=1)
144
154
 
145
155
  async def set_exposure(self, exposure: float) -> None:
146
156
  """Set the exposure time and acquire period.
@@ -164,7 +174,9 @@ class TetrammController(DetectorController):
164
174
  "Tetramm exposure time must be at least "
165
175
  f"{minimum_samples * sample_time}s, asked to set it to {exposure}s"
166
176
  )
167
- await self.driver.averaging_time.set(samples_per_reading * sample_time)
177
+ await self.driver.averaging_time.set(
178
+ samples_per_reading * sample_time
179
+ ) # correct
168
180
 
169
181
  async def start_acquiring_driver_and_ensure_status(self) -> AsyncStatus:
170
182
  """Start acquiring driver, raising ValueError if the detector is in a bad state.
@@ -202,10 +214,7 @@ class TetrammDatasetDescriber(DatasetDescriber):
202
214
  async def shape(self) -> tuple[int, int]:
203
215
  return (
204
216
  int(await self._driver.num_channels.get_value()),
205
- int(
206
- await self._driver.averaging_time.get_value()
207
- / await self._driver.sample_time.get_value(),
208
- ),
217
+ int(await self._driver.to_average.get_value()),
209
218
  )
210
219
 
211
220
 
@@ -223,7 +232,20 @@ class TetrammDetector(StandardDetector):
223
232
  ):
224
233
  self.driver = TetrammDriver(prefix + drv_suffix)
225
234
  self.file_io = NDFileHDFIO(prefix + fileio_suffix)
226
- controller = TetrammController(self.driver)
235
+ controller = TetrammController(self.driver, self.file_io)
236
+
237
+ self.current1 = epics_signal_r(float, prefix + "Cur1:MeanValue_RBV")
238
+ self.current2 = epics_signal_r(float, prefix + "Cur2:MeanValue_RBV")
239
+ self.current3 = epics_signal_r(float, prefix + "Cur3:MeanValue_RBV")
240
+ self.current4 = epics_signal_r(float, prefix + "Cur4:MeanValue_RBV")
241
+
242
+ self.sum_x = epics_signal_r(float, prefix + "SumX:MeanValue_RBV")
243
+ self.sum_y = epics_signal_r(float, prefix + "SumY:MeanValue_RBV")
244
+ self.sum_all = epics_signal_r(float, prefix + "SumAll:MeanValue_RBV")
245
+ self.diff_x = epics_signal_r(float, prefix + "DiffX:MeanValue_RBV")
246
+ self.diff_y = epics_signal_r(float, prefix + "DiffY:MeanValue_RBV")
247
+ self.pos_x = epics_signal_r(float, prefix + "PosX:MeanValue_RBV")
248
+ self.pos_y = epics_signal_r(float, prefix + "PosY:MeanValue_RBV")
227
249
 
228
250
  writer = ADHDFWriter(
229
251
  fileio=self.file_io,
dodal/devices/v2f.py ADDED
@@ -0,0 +1,39 @@
1
+ from ophyd_async.core import StandardReadable, StandardReadableFormat, StrictEnum
2
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
3
+
4
+
5
+ class V2FGain(StrictEnum):
6
+ LOW_NOISE3 = "10^3 low noise"
7
+ LOW_NOISE4 = "10^4 low noise"
8
+ LOW_NOISE5 = "10^5 low noise"
9
+ LOW_NOISE6 = "10^6 low noise"
10
+ LOW_NOISE7 = "10^7 low noise"
11
+ LOW_NOISE8 = "10^8 low noise"
12
+ LOW_NOISE9 = "10^9 low noise"
13
+ HIGH_SPEED5 = "10^5 high speed"
14
+ HIGH_SPEED6 = "10^6 high speed"
15
+ HIGH_SPEED7 = "10^7 high speed"
16
+ HIGH_SPEED8 = "10^8 high speed"
17
+ HIGH_SPEED9 = "10^9 high speed"
18
+ HIGH_SPEED10 = "10^10 high spd"
19
+ HIGH_SPEED11 = "10^11 high spd"
20
+
21
+
22
+ class QDV2F(StandardReadable):
23
+ """
24
+ A Quantum Detectors V2F low noise voltage to frequency converter.
25
+ Two channel V2F - 50mHz
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ prefix: str,
31
+ name: str = "",
32
+ I_suffix="I",
33
+ ) -> None:
34
+ with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
35
+ self.intensity = epics_signal_r(float, f"{prefix}{I_suffix}")
36
+ with self.add_children_as_readables():
37
+ self.gain = epics_signal_rw(V2FGain, f"{prefix}GAIN")
38
+
39
+ super().__init__(name)
@@ -82,6 +82,7 @@ class ArmDemand(Enum):
82
82
  DISARM = 0
83
83
 
84
84
 
85
+ # Replace with ophyd-async enum after https://github.com/bluesky/ophyd-async/pull/1067
85
86
  class SoftInState(StrictEnum):
86
87
  YES = "Yes"
87
88
  NO = "No"
@@ -77,7 +77,7 @@ class ZebraMapping(ZebraMappingValidations):
77
77
  Zebra's hardware configuration and wiring.
78
78
  """
79
79
 
80
- # Zebra ophyd signal for connection can be accessed
80
+ # Zebra ophyd signal for output can be accessed
81
81
  # with, eg, zebra.output.out_pvs[zebra.mapping.outputs.TTL_DETECTOR]
82
82
  outputs: ZebraTTLOutputs = ZebraTTLOutputs()
83
83
 
@@ -1,4 +1,4 @@
1
- from abc import ABC, abstractmethod
1
+ from abc import ABC
2
2
 
3
3
  from pydantic import BaseModel
4
4
 
@@ -9,7 +9,3 @@ class AbstractExperimentParameterBase(BaseModel, ABC):
9
9
 
10
10
  class AbstractExperimentWithBeamParams(AbstractExperimentParameterBase):
11
11
  transmission_fraction: float
12
-
13
- @abstractmethod
14
- def get_num_images(self) -> int:
15
- pass