dls-dodal 1.30.0__py3-none-any.whl → 1.31.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 (65) hide show
  1. {dls_dodal-1.30.0.dist-info → dls_dodal-1.31.0.dist-info}/METADATA +4 -4
  2. {dls_dodal-1.30.0.dist-info → dls_dodal-1.31.0.dist-info}/RECORD +64 -62
  3. {dls_dodal-1.30.0.dist-info → dls_dodal-1.31.0.dist-info}/WHEEL +1 -1
  4. dodal/_version.py +2 -2
  5. dodal/beamline_specific_utils/i03.py +1 -4
  6. dodal/beamlines/__init__.py +4 -0
  7. dodal/beamlines/i03.py +8 -8
  8. dodal/beamlines/i04.py +10 -9
  9. dodal/beamlines/i13_1.py +7 -7
  10. dodal/beamlines/i22.py +18 -18
  11. dodal/beamlines/p38.py +14 -14
  12. dodal/beamlines/p45.py +11 -11
  13. dodal/beamlines/training_rig.py +64 -0
  14. dodal/common/beamlines/beamline_parameters.py +5 -4
  15. dodal/common/beamlines/beamline_utils.py +9 -9
  16. dodal/common/types.py +4 -2
  17. dodal/common/udc_directory_provider.py +29 -22
  18. dodal/common/visit.py +59 -60
  19. dodal/devices/CTAB.py +1 -1
  20. dodal/devices/aperture.py +1 -1
  21. dodal/devices/aperturescatterguard.py +140 -188
  22. dodal/devices/areadetector/plugins/MJPG.py +2 -1
  23. dodal/devices/backlight.py +12 -1
  24. dodal/devices/dcm.py +1 -1
  25. dodal/devices/detector/detector.py +31 -30
  26. dodal/devices/detector/detector_motion.py +1 -1
  27. dodal/devices/fast_grid_scan.py +14 -24
  28. dodal/devices/focusing_mirror.py +2 -2
  29. dodal/devices/i22/dcm.py +1 -1
  30. dodal/devices/i22/fswitch.py +6 -2
  31. dodal/devices/i22/nxsas.py +32 -11
  32. dodal/devices/i24/aperture.py +1 -1
  33. dodal/devices/i24/beamstop.py +1 -1
  34. dodal/devices/i24/dcm.py +1 -1
  35. dodal/devices/i24/i24_detector_motion.py +1 -1
  36. dodal/devices/i24/pmac.py +24 -8
  37. dodal/devices/linkam3.py +1 -1
  38. dodal/devices/motors.py +1 -1
  39. dodal/devices/oav/oav_to_redis_forwarder.py +46 -17
  40. dodal/devices/robot.py +1 -2
  41. dodal/devices/scatterguard.py +1 -1
  42. dodal/devices/scintillator.py +1 -1
  43. dodal/devices/slits.py +1 -1
  44. dodal/devices/smargon.py +1 -1
  45. dodal/devices/tetramm.py +20 -16
  46. dodal/devices/training_rig/__init__.py +0 -0
  47. dodal/devices/training_rig/sample_stage.py +10 -0
  48. dodal/devices/turbo_slit.py +1 -1
  49. dodal/devices/undulator.py +1 -1
  50. dodal/devices/util/adjuster_plans.py +1 -1
  51. dodal/devices/util/save_panda.py +1 -1
  52. dodal/devices/util/test_utils.py +1 -1
  53. dodal/devices/xbpm_feedback.py +1 -2
  54. dodal/devices/xspress3/xspress3.py +1 -1
  55. dodal/devices/zebra.py +5 -0
  56. dodal/devices/zebra_controlled_shutter.py +24 -9
  57. dodal/devices/zocalo/zocalo_results.py +6 -2
  58. dodal/log.py +32 -10
  59. dodal/plans/check_topup.py +65 -10
  60. dodal/plans/data_session_metadata.py +8 -10
  61. dodal/plans/motor_util_plans.py +1 -1
  62. dodal/devices/beamstop.py +0 -8
  63. {dls_dodal-1.30.0.dist-info → dls_dodal-1.31.0.dist-info}/LICENSE +0 -0
  64. {dls_dodal-1.30.0.dist-info → dls_dodal-1.31.0.dist-info}/entry_points.txt +0 -0
  65. {dls_dodal-1.30.0.dist-info → dls_dodal-1.31.0.dist-info}/top_level.txt +0 -0
@@ -11,8 +11,12 @@ import workflows.recipe
11
11
  import workflows.transport
12
12
  from bluesky.protocols import Descriptor, Triggerable
13
13
  from numpy.typing import NDArray
14
- from ophyd_async.core import HintedSignal, StandardReadable, soft_signal_r_and_setter
15
- from ophyd_async.core.async_status import AsyncStatus
14
+ from ophyd_async.core import (
15
+ AsyncStatus,
16
+ HintedSignal,
17
+ StandardReadable,
18
+ soft_signal_r_and_setter,
19
+ )
16
20
  from workflows.transport.common_transport import CommonTransport
17
21
 
18
22
  from dodal.devices.zocalo.zocalo_interaction import _get_zocalo_connection
dodal/log.py CHANGED
@@ -8,28 +8,50 @@ from os import environ
8
8
  from pathlib import Path
9
9
  from typing import TypedDict
10
10
 
11
+ import colorlog
11
12
  from bluesky.log import logger as bluesky_logger
12
13
  from graypy import GELFTCPHandler
13
14
  from ophyd.log import logger as ophyd_logger
14
- from ophyd_async.log import (
15
- DEFAULT_DATE_FORMAT,
16
- DEFAULT_FORMAT,
17
- DEFAULT_LOG_COLORS,
18
- ColoredFormatterWithDeviceName,
19
- )
20
- from ophyd_async.log import logger as ophyd_async_logger
21
15
 
22
16
  LOGGER = logging.getLogger("Dodal")
17
+ # Temporarily duplicated https://github.com/bluesky/ophyd-async/issues/550
18
+ ophyd_async_logger = logging.getLogger("ophyd_async")
23
19
  LOGGER.setLevel(logging.DEBUG)
24
20
 
25
- DEFAULT_FORMATTER = ColoredFormatterWithDeviceName(
26
- fmt=DEFAULT_FORMAT, datefmt=DEFAULT_DATE_FORMAT, log_colors=DEFAULT_LOG_COLORS
27
- )
28
21
  ERROR_LOG_BUFFER_LINES = 20000
29
22
  INFO_LOG_DAYS = 30
30
23
  DEBUG_LOG_FILES_TO_KEEP = 7
31
24
  DEFAULT_GRAYLOG_PORT = 12231
32
25
 
26
+ # Temporarily duplicated https://github.com/bluesky/ophyd-async/issues/550
27
+ DEFAULT_FORMAT = (
28
+ "%(log_color)s[%(levelname)1.1s %(asctime)s.%(msecs)03d "
29
+ "%(module)s:%(lineno)d] %(message)s"
30
+ )
31
+
32
+ DEFAULT_DATE_FORMAT = "%y%m%d %H:%M:%S"
33
+
34
+ DEFAULT_LOG_COLORS = {
35
+ "DEBUG": "cyan",
36
+ "INFO": "green",
37
+ "WARNING": "yellow",
38
+ "ERROR": "red",
39
+ "CRITICAL": "red,bg_white",
40
+ }
41
+
42
+
43
+ class ColoredFormatterWithDeviceName(colorlog.ColoredFormatter):
44
+ def format(self, record):
45
+ message = super().format(record)
46
+ if device_name := getattr(record, "ophyd_async_device_name", None):
47
+ message = f"[{device_name}]{message}"
48
+ return message
49
+
50
+
51
+ DEFAULT_FORMATTER = ColoredFormatterWithDeviceName(
52
+ fmt=DEFAULT_FORMAT, datefmt=DEFAULT_DATE_FORMAT, log_colors=DEFAULT_LOG_COLORS
53
+ )
54
+
33
55
 
34
56
  class CircularMemoryHandler(logging.Handler):
35
57
  """Loosely based on the MemoryHandler, which keeps a buffer and writes it when full
@@ -1,5 +1,10 @@
1
+ from typing import Any
2
+
1
3
  import bluesky.plan_stubs as bps
2
4
 
5
+ from dodal.common.beamlines.beamline_parameters import (
6
+ get_beamline_parameters,
7
+ )
3
8
  from dodal.devices.synchrotron import Synchrotron, SynchrotronMode
4
9
  from dodal.log import LOGGER
5
10
 
@@ -7,6 +12,20 @@ ALLOWED_MODES = [SynchrotronMode.USER, SynchrotronMode.SPECIAL]
7
12
  DECAY_MODE_COUNTDOWN = -1 # Value of the start_countdown PV when in decay mode
8
13
  COUNTDOWN_DURING_TOPUP = 0
9
14
 
15
+ DEFAULT_THRESHOLD_EXPOSURE_S = 120
16
+ DEFAULT_TOPUP_GATE_DELAY_S = 1
17
+
18
+
19
+ class TopupConfig:
20
+ # For planned exposures less than this value, wait for topup to finish instead of
21
+ # collecting throughout topup.
22
+ THRESHOLD_EXPOSURE_S = "dodal_topup_threshold_exposure_s"
23
+ # Additional configurable safety margin to wait after the end of topup, as the start
24
+ # and end countdowns do not have the same precision, and in addition we want to be sure
25
+ # that collection does not overlap with any transients that may occur after the
26
+ # nominal endpoint.
27
+ TOPUP_GATE_DELAY_S = "dodal_topup_end_delay_s"
28
+
10
29
 
11
30
  def _in_decay_mode(time_to_topup):
12
31
  if time_to_topup == DECAY_MODE_COUNTDOWN:
@@ -23,15 +42,38 @@ def _gating_permitted(machine_mode: SynchrotronMode):
23
42
  return False
24
43
 
25
44
 
26
- def _delay_to_avoid_topup(total_run_time, time_to_topup):
27
- if total_run_time > time_to_topup:
28
- LOGGER.info(
29
- """
30
- Total run time for this collection exceeds time to next top up.
31
- Collection delayed until top up done.
32
- """
45
+ def _delay_to_avoid_topup(
46
+ total_run_time_s: float,
47
+ time_to_topup_s: float,
48
+ topup_configuration: dict,
49
+ total_exposure_time_s: float,
50
+ ) -> bool:
51
+ """Determine whether we should delay collection until after a topup. Generally
52
+ if a topup is due to occur during the collection we will delay collection until after the topup.
53
+ However for long-running collections, impact of the topup is potentially less and collection-duration may be
54
+ a significant fraction of the topup-interval, therefore we may wish to collect during a topup.
55
+
56
+ Args:
57
+ total_run_time_s: Anticipated time until end of the collection in seconds
58
+ time_to_topup_s: Time to the start of the topup as measured from the PV
59
+ topup_configuration: configuration dictionary
60
+ total_exposure_time_s: Total exposure time of the sample in s"""
61
+ if total_run_time_s > time_to_topup_s:
62
+ limit_s = topup_configuration.get(
63
+ TopupConfig.THRESHOLD_EXPOSURE_S, DEFAULT_THRESHOLD_EXPOSURE_S
33
64
  )
34
- return True
65
+ gate = total_exposure_time_s < limit_s
66
+ if gate:
67
+ LOGGER.info(f"""
68
+ Exposure time of {total_exposure_time_s}s below the threshold of {limit_s}s.
69
+ Collection delayed until topup done.
70
+ """)
71
+ else:
72
+ LOGGER.info(f"""
73
+ Exposure time of {total_exposure_time_s}s meets the threshold of {limit_s}s.
74
+ Collection proceeding through topup.
75
+ """)
76
+ return gate
35
77
  LOGGER.info(
36
78
  """
37
79
  Total run time less than time to next topup. Proceeding with collection.
@@ -71,12 +113,25 @@ def check_topup_and_wait_if_necessary(
71
113
  return
72
114
  tot_run_time = total_exposure_time + ops_time
73
115
  end_topup = yield from bps.rd(synchrotron.top_up_end_countdown)
74
- time_to_wait = (
75
- end_topup if _delay_to_avoid_topup(tot_run_time, time_to_topup) else 0.0
116
+ topup_configuration = _load_topup_configuration_from_properties_file()
117
+ should_wait = _delay_to_avoid_topup(
118
+ tot_run_time,
119
+ time_to_topup,
120
+ topup_configuration,
121
+ total_exposure_time,
122
+ )
123
+ topup_gate_delay = topup_configuration.get(
124
+ TopupConfig.TOPUP_GATE_DELAY_S, DEFAULT_TOPUP_GATE_DELAY_S
76
125
  )
126
+ time_to_wait = end_topup + topup_gate_delay if should_wait else 0.0
77
127
 
78
128
  yield from bps.sleep(time_to_wait)
79
129
 
80
130
  check_start = yield from bps.rd(synchrotron.top_up_start_countdown)
81
131
  if check_start == COUNTDOWN_DURING_TOPUP:
82
132
  yield from wait_for_topup_complete(synchrotron)
133
+
134
+
135
+ def _load_topup_configuration_from_properties_file() -> dict[str, Any]:
136
+ params = get_beamline_parameters()
137
+ return params.params
@@ -1,29 +1,28 @@
1
1
  from bluesky import plan_stubs as bps
2
2
  from bluesky import preprocessors as bpp
3
3
  from bluesky.utils import make_decorator
4
- from ophyd_async.core import DirectoryInfo
5
4
 
6
5
  from dodal.common.beamlines import beamline_utils
7
- from dodal.common.types import MsgGenerator, UpdatingDirectoryProvider
6
+ from dodal.common.types import MsgGenerator, UpdatingPathProvider
8
7
 
9
8
  DATA_SESSION = "data_session"
10
9
  DATA_GROUPS = "data_groups"
11
10
 
12
11
 
13
12
  def attach_data_session_metadata_wrapper(
14
- plan: MsgGenerator, provider: UpdatingDirectoryProvider | None = None
13
+ plan: MsgGenerator, provider: UpdatingPathProvider | None = None
15
14
  ) -> MsgGenerator:
16
15
  """
17
16
  Attach data session metadata to the runs within a plan and make it correlate
18
- with an ophyd-async DirectoryProvider.
17
+ with an ophyd-async PathProvider.
19
18
 
20
- This updates the directory provider (which in turn makes a call to to a service
19
+ This updates the path provider (which in turn makes a call to to a service
21
20
  to figure out which scan number we are using for such a scan), and ensures the
22
21
  start document contains the correct data session.
23
22
 
24
23
  Args:
25
24
  plan: The plan to preprocess
26
- provider: The directory provider that participating detectors are aware of.
25
+ provider: The path provider that participating detectors are aware of.
27
26
 
28
27
  Returns:
29
28
  MsgGenerator: A plan
@@ -32,13 +31,12 @@ def attach_data_session_metadata_wrapper(
32
31
  Iterator[Msg]: Plan messages
33
32
  """
34
33
  if provider is None:
35
- provider = beamline_utils.get_directory_provider()
34
+ provider = beamline_utils.get_path_provider()
36
35
  yield from bps.wait_for([provider.update])
37
- directory_info: DirectoryInfo = provider()
36
+ ress = yield from bps.wait_for([provider.data_session])
37
+ data_session = ress[0].result()
38
38
  # https://github.com/DiamondLightSource/dodal/issues/452
39
39
  # As part of 452, write each dataCollection into their own folder, then can use resource_dir directly
40
- assert directory_info.prefix is not None
41
- data_session = directory_info.prefix.removesuffix("-")
42
40
  yield from bpp.inject_md_wrapper(plan, md={DATA_SESSION: data_session})
43
41
 
44
42
 
@@ -6,7 +6,7 @@ from bluesky import plan_stubs as bps
6
6
  from bluesky.preprocessors import finalize_wrapper, pchain
7
7
  from bluesky.utils import Msg, make_decorator
8
8
  from ophyd_async.core import Device
9
- from ophyd_async.epics.motion import Motor
9
+ from ophyd_async.epics.motor import Motor
10
10
 
11
11
  from dodal.common import MsgGenerator
12
12
 
dodal/devices/beamstop.py DELETED
@@ -1,8 +0,0 @@
1
- from ophyd import Component as Cpt
2
- from ophyd import Device, EpicsMotor
3
-
4
-
5
- class BeamStop(Device):
6
- x = Cpt(EpicsMotor, "-MO-BS-01:X")
7
- y = Cpt(EpicsMotor, "-MO-BS-01:Y")
8
- z = Cpt(EpicsMotor, "-MO-BS-01:Z")