Skip to content

Commit 0c3ae27

Browse files
committed
devices/block
1 parent 2eedf44 commit 0c3ae27

File tree

7 files changed

+144
-73
lines changed

7 files changed

+144
-73
lines changed

src/ibex_bluesky_core/devices/__init__.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
"""Bluesky devices and device-related utilities.
1+
"""ISIS-specific bluesky devices and device-related utilities.
22
3-
Foo bar baz.
3+
The devices in this module are implemented using the :py:obj:`ophyd_async` library,
4+
which in turn reads or writes to the underlying EPICS PVs to control equipment.
45
"""
56

67
from __future__ import annotations
@@ -45,13 +46,19 @@ def compress_and_hex(value: str) -> bytes:
4546

4647

4748
def isis_epics_signal_rw(datatype: type[T], read_pv: str, name: str = "") -> SignalRW[T]:
48-
"""Make a RW signal with ISIS' PV naming standard ie. read_pv as TITLE, write_pv as TITLE:SP."""
49+
"""Make a RW signal with ISIS' PV naming standard.
50+
51+
For a pv like ``IN:INSTNAME:SOME_PARAMETER``:
52+
53+
- The ``read_pv`` will be set to ``IN:INSTNAME:SOME_PARAMETER``
54+
- The ``write_pv`` will be set to ``IN:INSTNAME:SOME_PARAMETER:SP``
55+
"""
4956
write_pv = f"{read_pv}:SP"
5057
return epics_signal_rw(datatype, read_pv, write_pv, name)
5158

5259

5360
class NoYesChoice(StrictEnum):
54-
"""No-Yes enum for an mbbi/mbbo or bi/bo with capitalised "No" and "Yes" options."""
61+
"""No-Yes enum for an mbbi/mbbo or bi/bo with capitalised "No"/"Yes" options."""
5562

5663
NO = "No"
5764
YES = "Yes"

src/ibex_bluesky_core/devices/block.py

Lines changed: 128 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""ophyd-async devices and utilities for communicating with IBEX blocks."""
1+
"""bluesky devices and utilities for communicating with IBEX blocks."""
22

33
import asyncio
44
import logging
@@ -62,71 +62,85 @@
6262
class BlockWriteConfig(Generic[T]):
6363
"""Configuration settings for writing to blocks.
6464
65-
use_completion_callback:
66-
Whether to wait for an EPICS completion callback while setting
67-
this block. Defaults to true, which is appropriate for most blocks.
65+
These settings control how a block write is determined to be 'complete'. A block
66+
write should only be marked as complete when the equipment has physically reached
67+
the correct state, not just when the equipment has been told to move to the correct
68+
state.
6869
69-
set_success_func:
70-
An arbitrary function which is called to decide whether the block has
71-
set successfully yet or not. The function takes (setpoint, actual) as arguments and
72-
should return true if the value has successfully set and is "ready", or False otherwise.
73-
74-
This can be used to implement arbitrary tolerance behaviour. For example::
75-
76-
def check(setpoint: T, actual: T) -> bool:
77-
return setpoint - 0.1 <= actual <= setpoint + 0.1
70+
For example, during a scan of a block against a detector, the detector will be read
71+
as soon as the block declares the write as 'complete'.
72+
"""
7873

79-
If use_completion_callback is True, the completion callback must complete before
80-
set_success_func is ever called.
74+
use_completion_callback: bool = True
75+
"""
76+
Whether to wait for an EPICS completion callback while setting
77+
this block. Defaults to :py:obj:`True`, which is appropriate for most blocks.
78+
"""
8179

82-
Executing this function should be "fast" (i.e. the function should not sleep), and it should
83-
not do any external I/O.
80+
set_success_func: Callable[[T, T], bool] | None = None
81+
"""
82+
An arbitrary function which is called to decide whether the block has
83+
set successfully yet or not. The function takes ``(setpoint, actual)`` as arguments and
84+
should return :py:obj:`True` if the value has successfully set and is "ready", or
85+
:py:obj:`False` otherwise.
8486
85-
Defaults to None, which means no check is applied.
87+
This can be used to implement arbitrary tolerance behaviour. For example::
8688
87-
set_timeout_s:
88-
A timeout, in seconds, on the value being set successfully. The timeout
89-
applies to the EPICS completion callback (if enabled) and the set success function
90-
(if provided), and excludes any configured settle time.
89+
def check(setpoint: T, actual: T) -> bool:
90+
return setpoint - 0.1 <= actual <= setpoint + 0.1
9191
92-
Defaults to None, which means no timeout.
92+
If use_completion_callback is True, the completion callback must complete before
93+
``set_success_func`` is ever called.
9394
94-
settle_time_s:
95-
A wait time, in seconds, which is unconditionally applied just before the set
96-
status is marked as complete. Defaults to zero.
95+
Executing this function should be "fast" (i.e. the function should not sleep), and it should
96+
not do any external I/O.
9797
98-
use_global_moving_flag:
99-
Whether to wait for the IBEX global moving indicator to return "stationary". This is useful
100-
for compound moves, where changing a single block may cause multiple underlying axes to
101-
move, and all movement needs to be complete before the set is considered complete. Defaults
102-
to False.
98+
Defaults to :py:obj:`None`, which means no check is applied.
99+
"""
103100

104-
timeout_is_error:
105-
Whether a write timeout is considered an error. Defaults to True. If False, a set will be
106-
marked as complete without error even if the block has not given a completion callback or
107-
satisfied set_success_func within settle_time_s.
101+
set_timeout_s: float | None = None
102+
"""
103+
A timeout, in seconds, on the value being set successfully. The timeout
104+
applies to the EPICS completion callback (if enabled) and the set success function
105+
(if provided), and excludes any configured settle time.
108106
107+
Defaults to :py:obj:`None`, which means no timeout.
109108
"""
110109

111-
use_completion_callback: bool = True
112-
set_success_func: Callable[[T, T], bool] | None = None
113-
set_timeout_s: float | None = None
114110
settle_time_s: float = 0.0
111+
"""
112+
A wait time, in seconds, which is unconditionally applied just before the set
113+
status is marked as complete. Defaults to zero.
114+
"""
115+
115116
use_global_moving_flag: bool = False
117+
"""
118+
Whether to wait for the IBEX global moving indicator to return "stationary". This is useful
119+
for compound moves, where changing a single block may cause multiple underlying axes to
120+
move, and all movement needs to be complete before the set is considered complete.
121+
"""
122+
116123
timeout_is_error: bool = True
124+
"""
125+
Whether a write timeout is considered an error. Defaults to True. If False, a set will be
126+
marked as complete without error even if the block has not given a completion callback or
127+
satisfied ``set_success_func`` within ``settle_time_s``.
128+
"""
117129

118130

119131
class RunControl(StandardReadable):
120132
"""Subdevice for common run-control signals."""
121133

122134
def __init__(self, prefix: str, name: str = "") -> None:
123-
"""Create a run control wrapper for a block.
135+
"""Subdevice for common run-control signals.
136+
137+
.. note::
124138
125-
Usually run control should be accessed via the run_control property on a block, rather
126-
than by constructing an instance of this class directly.
139+
Run control should be accessed via the ``run_control`` property on a block,
140+
rather than by constructing an instance of this class directly.
127141
128142
Args:
129-
prefix: the run-control prefix, e.g. "IN:INSTRUMENT:CS:SB:blockname:RC:"
143+
prefix: the run-control prefix, e.g. ``IN:INSTRUMENT:CS:SB:blockname:RC:``
130144
name: ophyd device name
131145
132146
"""
@@ -148,13 +162,14 @@ def __init__(self, prefix: str, name: str = "") -> None:
148162

149163

150164
class BlockR(StandardReadable, Triggerable, Generic[T]):
151-
"""Device representing an IBEX readable block of arbitrary data type."""
165+
"""Read-only block."""
152166

153167
def __init__(self, datatype: type[T], prefix: str, block_name: str) -> None:
154-
"""Create a new read-only block.
168+
"""Device representing an IBEX readable block of arbitrary data type.
155169
156170
Args:
157-
datatype: the type of data in this block (e.g. str, int, float)
171+
datatype: the type of data in this block
172+
(e.g. :py:obj:`str`, :py:obj:`int`, :py:obj:`float`)
158173
prefix: the current instrument's PV prefix
159174
block_name: the name of the block
160175
@@ -170,9 +185,13 @@ def __init__(self, datatype: type[T], prefix: str, block_name: str) -> None:
170185

171186
@AsyncStatus.wrap
172187
async def trigger(self) -> None:
173-
"""Blocks need to be triggerable to be used in adaptive scans.
188+
"""Blocks do not do anything when triggered.
174189
175-
They do not do anything when triggered.
190+
This method implements :py:obj:`bluesky.protocols.Triggerable`,
191+
and should not be called directly.
192+
193+
This empty implementation is provided to allow using blocks in
194+
adaptive scans.
176195
"""
177196

178197
def __repr__(self) -> str:
@@ -192,16 +211,22 @@ def __init__(
192211
write_config: BlockWriteConfig[T] | None = None,
193212
sp_suffix: str = ":SP",
194213
) -> None:
195-
"""Create a new read-write block.
214+
"""Device representing an IBEX read/write block of arbitrary data type.
215+
216+
The setpoint is not added to ``read()`` by default. For most cases where setpoint readback
217+
functionality is desired, :py:obj:`~ibex_bluesky_core.devices.block.BlockRwRbv` is a more
218+
suitable type.
196219
197-
The setpoint is not added to read() by default. For most cases where setpoint readback
198-
functionality is desired, BlockRwRbv is a more suitable type.
220+
If you *explicitly* need to read the setpoint from a
221+
:py:obj:`~ibex_bluesky_core.devices.block.BlockRw`, you can do so in a plan with:
199222
200-
If you *explicitly* need to read the setpoint from a BlockRw, you can do so in a plan with::
223+
.. code-block:: python
201224
202225
import bluesky.plan_stubs as bps
203-
block: BlockRw = ...
204-
bps.read(block.setpoint)
226+
227+
def plan():
228+
block: BlockRw = ...
229+
yield from bps.rd(block.setpoint)
205230
206231
But note that this does not read back the setpoint from hardware, but rather the setpoint
207232
which was last sent by EPICS.
@@ -230,7 +255,21 @@ def __init__(
230255

231256
@AsyncStatus.wrap
232257
async def set(self, value: T) -> None:
233-
"""Set the setpoint of this block."""
258+
"""Set the setpoint of this block.
259+
260+
This method implements :py:obj:`bluesky.protocols.Movable`, and should not be
261+
called directly.
262+
263+
From a plan, set a block using:
264+
265+
.. code-block:: python
266+
267+
import bluesky.plan_stubs as bps
268+
269+
def my_plan():
270+
block = BlockRw(...)
271+
yield from bps.mv(block, value)
272+
"""
234273

235274
async def do_set(setpoint: T) -> None:
236275
logger.info("Setting Block %s to %s", self.name, setpoint)
@@ -305,10 +344,10 @@ def __init__(
305344
*,
306345
write_config: BlockWriteConfig[T] | None = None,
307346
) -> None:
308-
"""Create a new read/write/setpoint readback block.
347+
"""Device representing an IBEX read/write/setpoint readback block of arbitrary data type.
309348
310-
The setpoint readback is added to read(), but not hints(), by default. If you do not need
311-
a setpoint readback, choose BlockRw instead of BlockRwRbv.
349+
The setpoint readback is added to read(), but not hints(), by default. If you do not have
350+
a setpoint readback, use :py:obj:`~ibex_bluesky_core.devices.block.BlockRw` instead.
312351
313352
Args:
314353
datatype: the type of data in this block (e.g. str, int, float)
@@ -327,7 +366,32 @@ def __init__(
327366
)
328367

329368
async def locate(self) -> Location[T]:
330-
"""Get the current 'location' of this block."""
369+
"""Get the current :py:obj:`~bluesky.protocols.Location` of this block.
370+
371+
This method implements :py:obj:`bluesky.protocols.Locatable`, and should not be
372+
called directly.
373+
374+
From a plan, locate a block using:
375+
376+
.. code-block:: python
377+
378+
import bluesky.plan_stubs as bps
379+
380+
def my_plan():
381+
block: BlockRwRbv = ...
382+
location = yield from bps.locate(block)
383+
384+
If you only need the current value, rather than the value and the setpoint-readback, use:
385+
386+
.. code-block:: python
387+
388+
import bluesky.plan_stubs as bps
389+
390+
def my_plan():
391+
block: BlockRwRbv = ...
392+
value = yield from bps.rd(block)
393+
394+
"""
331395
logger.info("locating block %s", self.name)
332396
actual, sp_rbv = await asyncio.gather(
333397
self.readback.get_value(),
@@ -401,7 +465,7 @@ def __repr__(self) -> str:
401465
def set( # pyright: ignore
402466
self, value: float, timeout: CalculatableTimeout = CALCULATE_TIMEOUT
403467
) -> WatchableAsyncStatus[float]:
404-
"""Pass through set to superclass.
468+
"""Pass through ``set`` to :py:obj:`ophyd_async.epics.motor.Motor.set`.
405469
406470
This is needed so that type-checker correctly understands the type of set.
407471
@@ -415,7 +479,7 @@ def set( # pyright: ignore
415479
def block_r(datatype: type[T], block_name: str) -> BlockR[T]:
416480
"""Get a local read-only block for the current instrument.
417481
418-
See documentation of BlockR for more information.
482+
See documentation of :py:obj:`~ibex_bluesky_core.devices.block.BlockR` for more information.
419483
"""
420484
return BlockR(datatype=datatype, prefix=get_pv_prefix(), block_name=block_name)
421485

@@ -429,7 +493,7 @@ def block_rw(
429493
) -> BlockRw[T]:
430494
"""Get a local read-write block for the current instrument.
431495
432-
See documentation of BlockRw for more information.
496+
See documentation of :py:obj:`~ibex_bluesky_core.devices.block.BlockRw` for more information.
433497
"""
434498
return BlockRw(
435499
datatype=datatype,
@@ -445,7 +509,7 @@ def block_w(
445509
) -> BlockRw[T]:
446510
"""Get a write-only block for the current instrument.
447511
448-
This is actually just :obj:`ibex_bluesky_core.devices.block.BlockRw` but with no SP suffix.
512+
This is a :py:obj:`~ibex_bluesky_core.devices.block.BlockRw` instance with no SP suffix.
449513
"""
450514
return BlockRw(
451515
datatype=datatype,
@@ -461,7 +525,7 @@ def block_rw_rbv(
461525
) -> BlockRwRbv[T]:
462526
"""Get a local read/write/setpoint readback block for the current instrument.
463527
464-
See documentation of BlockRwRbv for more information.
528+
See documentation of :py:obj:`~ibex_bluesky_core.devices.block.BlockRwRbv` for more information.
465529
"""
466530
return BlockRwRbv(
467531
datatype=datatype, prefix=get_pv_prefix(), block_name=block_name, write_config=write_config
@@ -471,6 +535,6 @@ def block_rw_rbv(
471535
def block_mot(block_name: str) -> BlockMot:
472536
"""Get a local block pointing at a motor record for the local instrument.
473537
474-
See documentation of BlockMot for more information.
538+
See documentation of :py:obj:`~ibex_bluesky_core.devices.block.BlockMot` for more information.
475539
"""
476540
return BlockMot(prefix=get_pv_prefix(), block_name=block_name)

src/ibex_bluesky_core/devices/dae/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Utilities for the DAE device - mostly XML helpers."""
1+
"""Low-level bluesky device interface to the DAE."""
22

33
import asyncio
44
from typing import Generic, TypeVar

src/ibex_bluesky_core/devices/muon.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Muon specific bluesky device helpers."""
1+
"""Muon-specific bluesky devices and utilities."""
22

33
import asyncio
44
import logging

src/ibex_bluesky_core/devices/polarisingdae/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""An interface to the DAE for bluesky, suited for polarisation."""
1+
"""Specialised DAE interface for polarisation measurements."""
22

33
import logging
44
from typing import Generic, TypeAlias

src/ibex_bluesky_core/devices/reflectometry.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Devices specific to Reflectometry beamlines."""
1+
"""Reflectometry-specific bluesky devices and utilities."""
22

33
import asyncio
44
import logging

src/ibex_bluesky_core/devices/simpledae/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""A simple interface to the DAE for bluesky."""
1+
"""High-level bluesky device interface to the DAE for 'typical' measurements."""
22

33
import logging
44
from typing import Generic

0 commit comments

Comments
 (0)