Skip to content

Commit f1e6453

Browse files
authored
Merge pull request #13 from ISISComputingGroup/10_add_block_signals
Add basic block signals
2 parents 3409dab + cd763c4 commit f1e6453

File tree

11 files changed

+759
-47
lines changed

11 files changed

+759
-47
lines changed

doc/blocks.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Blocks
2+
3+
Blocks are one of IBEX's central abstractions, which present a uniform interface to any
4+
scientifically interesting PV.
5+
6+
`ibex_bluesky_core` has support for four types of blocks:
7+
- Read-only
8+
- Read/write
9+
- Read/write with setpoint readback
10+
- Motors
11+
12+
> **_ℹ️_**
13+
> All signals, including blocks, in bluesky have a strong type. This must match
14+
> the underlying EPICS type of the PV, which helps to catch problems up-front rather than
15+
> the middle of a plan. Example error at the start of a plan, from trying to connect a `str` block to a `float` PV:
16+
> ```
17+
> ophyd_async.core._utils.NotConnected:
18+
> mot: NotConnected:
19+
> setpoint_readback: TypeError: TE:NDW2922:CS:SB:mot:SP:RBV has type float not str
20+
> setpoint: TypeError: TE:NDW2922:CS:SB:mot:SP has type float not str
21+
> readback: TypeError: TE:NDW2922:CS:SB:mot has type float not str
22+
> ```
23+
24+
## Block types
25+
26+
### `block_r` (read-only)
27+
28+
This is a read-only block. It supports `bluesky`'s `Readable` protocol, as well as
29+
basic metadata protocols such as `HasName`.
30+
31+
This type of block is usable by:
32+
- Plans like `bluesky.plans.count()` or `bluesky.plans.scan()` as a detector object.
33+
- Plan stubs like `bluesky.plan_stubs.rd()`, which plans may use to get the current value
34+
of a block easily for use in the plan.
35+
36+
A `BlockR` object does not implement any logic on read - it simply returns the most recent
37+
value of the block.
38+
39+
A simple constructor, `block_r`, is available, which assumes the current instrument's PV
40+
prefix:
41+
42+
```python
43+
from ibex_bluesky_core.devices.block import block_r
44+
readable_block = block_r(float, "my_block_name")
45+
```
46+
47+
### `block_rw` (read, write)
48+
49+
This is a read-write block. It supports all of the same protocols as `BlockR`, with the
50+
addition of the `Movable` protocol.
51+
52+
The addition of the movable protocol means that this type of block can be moved by plan
53+
stubs such as `bluesky.plan_stubs.mv()` or `bluesky.plan_stubs.abs_set()`.
54+
55+
It can also be used as the `Movable` in full plans like `bluesky.plans.scan()`.
56+
57+
> **_ℹ️_**
58+
> In bluesky terminology, any object with a `set()` method is `Movable`. Therefore, a
59+
> temperature controller is "moved" from one temperature to another, and a run title
60+
> may equally be "moved" from one title to another.
61+
>
62+
> This is simply a matter of terminology - bluesky fully supports moving things which
63+
> are not motors, even if the documentation tends to use motors as the examples.
64+
65+
Like `block_r`, a simple constructor is available:
66+
67+
```python
68+
from ibex_bluesky_core.devices.block import block_rw, BlockWriteConfig
69+
writable_block = block_rw(
70+
float,
71+
"my_block_name",
72+
# Example: configure to always wait 5 seconds after being set.
73+
# For further options, see docstring of BlockWriteConfig.
74+
write_config=BlockWriteConfig(settle_time_s=5.0)
75+
)
76+
```
77+
78+
### `block_rw_rbv` (read, write, setpoint readback)
79+
80+
This is a block with full support for reading and writing as per `BlockRw`, but with
81+
the addition of `bluesky`'s `Locatable` protocol, which allows you to read back the
82+
current setpoint. Where possible, the setpoint will be read back from hardware.
83+
84+
This object is suitable for use in plan stubs such as `bluesky.plan_stubs.locate()`.
85+
86+
This object is also more suitable for use in plans which use relative moves - the
87+
relative move will be calculated with respect to the setpoint readback from hardware
88+
(if available).
89+
90+
Just like `block_rw`, a simple constructor is available:
91+
92+
```python
93+
from ibex_bluesky_core.devices.block import block_rw_rbv, BlockWriteConfig
94+
rw_rbv_block = block_rw_rbv(
95+
float,
96+
"my_block_name",
97+
# Example: configure to always wait 5 seconds after being set.
98+
# For further options, see docstring of BlockWriteConfig.
99+
write_config=BlockWriteConfig(settle_time_s=5.0)
100+
)
101+
```
102+
103+
### `block_mot` (motor-specific)
104+
105+
This represents a block pointing at a motor record. This has support for:
106+
- Reading (`Readable`)
107+
- Writing (`Movable`)
108+
- Limit-checking (`Checkable`)
109+
- Stopping (e.g. on scan abort) (`Stoppable`)
110+
- And advanced use-cases like fly-scanning
111+
112+
This type is recommended to be used if the underlying block is a motor record. It always has
113+
type `float`, and as such does not take a type argument (unlike the other block types).
114+
115+
`Checkable` means that moves which would eventually violate limits can be detected by
116+
bluesky simulators, before the plan ever runs. This can help to catch errors before
117+
the plan is executed against hardware.
118+
119+
`Stoppable` means that the motor can be asked to stop by bluesky. Plans may choose to execute
120+
a `stop()` on failure, or explicitly during a plan.
121+
122+
A `block_mot` can be made in a similar way to the other block types; however, it does not
123+
require an explicit type as motors are always of `float` data type:
124+
125+
```python
126+
from ibex_bluesky_core.devices.block import block_mot
127+
mot_block = block_mot("motor_block")
128+
```
129+
130+
## Configuring block write behaviour
131+
132+
`BlockRw` and `BlockRwRbv` both take a `write_config` argument, which can be used to configure
133+
the behaviour on writing to a block, for example tolerances and settle times.
134+
135+
See the docstring on `ibex_bluesky_core.devices.block.BlockWriteConfig` for a detailed
136+
description of all the options which are available.
137+
138+
## Run control
139+
140+
Run control information is available via the `block.run_control` sub-device.
141+
142+
Both configuring and reading the current status of run control are permitted.
143+
144+
> **_ℹ️_**
145+
> Run control limits are always `float`, regardless of the datatype of the block.

pyproject.toml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ dependencies = [
4747

4848
[project.optional-dependencies]
4949
dev = [
50-
"ruff",
50+
"ruff>=0.6",
5151
"pyright",
5252
"pytest",
5353
"pytest-asyncio",
@@ -79,7 +79,17 @@ directory = "coverage_html_report"
7979

8080
[tool.pyright]
8181
include = ["src", "tests"]
82-
reportUntypedFunctionDecorator = true
82+
reportConstantRedefinition = true
83+
reportDeprecated = true
84+
reportInconsistentConstructor = true
85+
reportMissingParameterType = true
86+
reportMissingTypeArgument = true
87+
reportUnnecessaryCast = true
88+
reportUnnecessaryComparison = true
89+
reportUnnecessaryContains = true
90+
reportUnnecessaryIsInstance = true
91+
reportUntypedBaseClass = true
8392
reportUntypedClassDecorator = true
93+
reportUntypedFunctionDecorator = true
8494

8595
[tool.setuptools_scm]

src/ibex_bluesky_core/callbacks/document_logger.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import json
44
from pathlib import Path
5+
from typing import Any
56

67
log_location = Path("C:\\") / "instrument" / "var" / "logs" / "bluesky" / "raw_documents"
78

@@ -14,7 +15,7 @@ def __init__(self) -> None:
1415
self.current_start_document = None
1516
self.filename = None
1617

17-
def __call__(self, name: str, document: dict) -> None:
18+
def __call__(self, name: str, document: dict[str, Any]) -> None:
1819
"""Is called when a new document needs to be processed. Writes document to a file.
1920
2021
Args:
@@ -31,7 +32,7 @@ def __call__(self, name: str, document: dict) -> None:
3132
assert self.filename is not None, "Could not create filename."
3233
assert self.current_start_document is not None, "Saw a non-start document before a start."
3334

34-
to_write = {"type": name, "document": document}
35+
to_write: dict[str, Any] = {"type": name, "document": document}
3536

3637
with open(self.filename, "a") as outfile:
3738
outfile.write(f"{json.dumps(to_write)}\n")

src/ibex_bluesky_core/demo_plan.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from ophyd_async.plan_stubs import ensure_connected
1010

1111
from ibex_bluesky_core.devices import get_pv_prefix
12-
from ibex_bluesky_core.devices.block import Block
12+
from ibex_bluesky_core.devices.block import BlockRwRbv, block_rw_rbv
1313
from ibex_bluesky_core.devices.dae import Dae
1414
from ibex_bluesky_core.run_engine import get_run_engine
1515

@@ -28,12 +28,12 @@ def run_demo_plan() -> None:
2828
"""
2929
RE = get_run_engine()
3030
prefix = get_pv_prefix()
31-
block = Block(prefix, "mot", float)
31+
block = block_rw_rbv(float, "mot")
3232
dae = Dae(prefix)
3333
RE(demo_plan(block, dae), LiveTable(["mot", "DAE"]))
3434

3535

36-
def demo_plan(block: Block, dae: Dae) -> Generator[Msg, None, None]:
36+
def demo_plan(block: BlockRwRbv[float], dae: Dae) -> Generator[Msg, None, None]:
3737
"""Demonstration plan which moves a block and reads the DAE."""
3838
yield from ensure_connected(block, dae, force_reconnect=True)
3939

0 commit comments

Comments
 (0)