Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic block signals #13

Merged
merged 24 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bf4a024
Add basic block signals
Tom-Willemsen Aug 10, 2024
cd73676
Add unit tests for block writing
Tom-Willemsen Aug 11, 2024
3f93b6f
Fix test for python 3.10
Tom-Willemsen Aug 11, 2024
de22d3d
Add utility constructors with local prefix assumed
Tom-Willemsen Aug 11, 2024
317a1c0
Add runcontrol tests
Tom-Willemsen Aug 11, 2024
05c61d6
Remove setpoint from default read() on BlockRw
Tom-Willemsen Aug 11, 2024
0356f48
Support motor blocks
Tom-Willemsen Aug 11, 2024
863db7b
Improve naming
Tom-Willemsen Aug 11, 2024
768c935
Add user-facing documentation
Tom-Willemsen Aug 11, 2024
7ad1e76
Add run control on motor blocks
Tom-Willemsen Aug 11, 2024
8111e96
Add basic block signals
Tom-Willemsen Aug 10, 2024
4e065a1
Add unit tests for block writing
Tom-Willemsen Aug 11, 2024
ac30d4c
Fix test for python 3.10
Tom-Willemsen Aug 11, 2024
e517d60
Add utility constructors with local prefix assumed
Tom-Willemsen Aug 11, 2024
0590ad6
Add runcontrol tests
Tom-Willemsen Aug 11, 2024
af15475
Remove setpoint from default read() on BlockRw
Tom-Willemsen Aug 11, 2024
39fc3de
Support motor blocks
Tom-Willemsen Aug 11, 2024
9b54066
Improve naming
Tom-Willemsen Aug 11, 2024
408acce
Add user-facing documentation
Tom-Willemsen Aug 11, 2024
a88eb29
Add run control on motor blocks
Tom-Willemsen Aug 11, 2024
9f15764
Merge branch '10_add_block_signals' of https://github.com/IsisComputi…
Tom-Willemsen Aug 19, 2024
776554d
Implement Triggerable for blocks to allow use in adaptive scans
Tom-Willemsen Aug 19, 2024
d75917f
Add more docs
Tom-Willemsen Aug 20, 2024
cd763c4
Merge branch 'main' into 10_add_block_signals
jackbdoughty Aug 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions doc/blocks.md
Tom-Willemsen marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Blocks

Blocks are one of IBEX's central abstractions, which present a uniform interface to any
scientifically interesting PV.

`ibex_bluesky_core` has support for four types of blocks:
- Read-only
- Read/write
- Read/write with setpoint readback
- Motors

> **_ℹ️_**
> All signals, including blocks, in bluesky have a strong type. This must match
> the underlying EPICS type of the PV, which helps to catch problems up-front rather than
> the middle of a plan. Example error at the start of a plan, from trying to connect a `str` block to a `float` PV:
> ```
> ophyd_async.core._utils.NotConnected:
> mot: NotConnected:
> setpoint_readback: TypeError: TE:NDW2922:CS:SB:mot:SP:RBV has type float not str
> setpoint: TypeError: TE:NDW2922:CS:SB:mot:SP has type float not str
> readback: TypeError: TE:NDW2922:CS:SB:mot has type float not str
> ```

## Block types

### `block_r` (read-only)

This is a read-only block. It supports `bluesky`'s `Readable` protocol, as well as
basic metadata protocols such as `HasName`.

This type of block is usable by:
- Plans like `bluesky.plans.count()` or `bluesky.plans.scan()` as a detector object.
- Plan stubs like `bluesky.plan_stubs.rd()`, which plans may use to get the current value
of a block easily for use in the plan.

A `BlockR` object does not implement any logic on read - it simply returns the most recent
value of the block.

A simple constructor, `block_r`, is available, which assumes the current instrument's PV
prefix:

```python
from ibex_bluesky_core.devices.block import block_r
readable_block = block_r(float, "my_block_name")
```

### `block_rw` (read, write)

This is a read-write block. It supports all of the same protocols as `BlockR`, with the
addition of the `Movable` protocol.

The addition of the movable protocol means that this type of block can be moved by plan
stubs such as `bluesky.plan_stubs.mv()` or `bluesky.plan_stubs.abs_set()`.

It can also be used as the `Movable` in full plans like `bluesky.plans.scan()`.

> **_ℹ️_**
> In bluesky terminology, any object with a `set()` method is `Movable`. Therefore, a
> temperature controller is "moved" from one temperature to another, and a run title
> may equally be "moved" from one title to another.
>
> This is simply a matter of terminology - bluesky fully supports moving things which
> are not motors, even if the documentation tends to use motors as the examples.

Like `block_r`, a simple constructor is available:

```python
from ibex_bluesky_core.devices.block import block_rw, BlockWriteConfig
writable_block = block_rw(
float,
"my_block_name",
# Example: configure to always wait 5 seconds after being set.
# For further options, see docstring of BlockWriteConfig.
write_config=BlockWriteConfig(settle_time_s=5.0)
)
```

### `block_rw_rbv` (read, write, setpoint readback)

This is a block with full support for reading and writing as per `BlockRw`, but with
the addition of `bluesky`'s `Locatable` protocol, which allows you to read back the
current setpoint. Where possible, the setpoint will be read back from hardware.

This object is suitable for use in plan stubs such as `bluesky.plan_stubs.locate()`.

This object is also more suitable for use in plans which use relative moves - the
relative move will be calculated with respect to the setpoint readback from hardware
(if available).

Just like `block_rw`, a simple constructor is available:

```python
from ibex_bluesky_core.devices.block import block_rw_rbv, BlockWriteConfig
rw_rbv_block = block_rw_rbv(
float,
"my_block_name",
# Example: configure to always wait 5 seconds after being set.
# For further options, see docstring of BlockWriteConfig.
write_config=BlockWriteConfig(settle_time_s=5.0)
)
```

### `block_mot` (motor-specific)

This represents a block pointing at a motor record. This has support for:
- Reading (`Readable`)
- Writing (`Movable`)
- Limit-checking (`Checkable`)
- Stopping (e.g. on scan abort) (`Stoppable`)
- And advanced use-cases like fly-scanning

This type is recommended to be used if the underlying block is a motor record. It always has
type `float`, and as such does not take a type argument (unlike the other block types).

`Checkable` means that moves which would eventually violate limits can be detected by
bluesky simulators, before the plan ever runs. This can help to catch errors before
the plan is executed against hardware.

`Stoppable` means that the motor can be asked to stop by bluesky. Plans may choose to execute
a `stop()` on failure, or explicitly during a plan.

A `block_mot` can be made in a similar way to the other block types; however, it does not
require an explicit type as motors are always of `float` data type:

```python
from ibex_bluesky_core.devices.block import block_mot
mot_block = block_mot("motor_block")
```

## Configuring block write behaviour

`BlockRw` and `BlockRwRbv` both take a `write_config` argument, which can be used to configure
the behaviour on writing to a block, for example tolerances and settle times.

See the docstring on `ibex_bluesky_core.devices.block.BlockWriteConfig` for a detailed
description of all the options which are available.

## Run control

Run control information is available via the `block.run_control` sub-device.

Both configuring and reading the current status of run control are permitted.

> **_ℹ️_**
> Run control limits are always `float`, regardless of the datatype of the block.
14 changes: 12 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ dependencies = [

[project.optional-dependencies]
dev = [
"ruff",
"ruff>=0.6",
"pyright",
"pytest",
"pytest-asyncio",
Expand Down Expand Up @@ -79,7 +79,17 @@ directory = "coverage_html_report"

[tool.pyright]
include = ["src", "tests"]
reportUntypedFunctionDecorator = true
reportConstantRedefinition = true
reportDeprecated = true
reportInconsistentConstructor = true
reportMissingParameterType = true
reportMissingTypeArgument = true
reportUnnecessaryCast = true
reportUnnecessaryComparison = true
reportUnnecessaryContains = true
reportUnnecessaryIsInstance = true
reportUntypedBaseClass = true
reportUntypedClassDecorator = true
reportUntypedFunctionDecorator = true

[tool.setuptools_scm]
5 changes: 3 additions & 2 deletions src/ibex_bluesky_core/callbacks/document_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import json
from pathlib import Path
from typing import Any

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

Expand All @@ -14,7 +15,7 @@ def __init__(self) -> None:
self.current_start_document = None
self.filename = None

def __call__(self, name: str, document: dict) -> None:
def __call__(self, name: str, document: dict[str, Any]) -> None:
"""Is called when a new document needs to be processed. Writes document to a file.

Args:
Expand All @@ -31,7 +32,7 @@ def __call__(self, name: str, document: dict) -> None:
assert self.filename is not None, "Could not create filename."
assert self.current_start_document is not None, "Saw a non-start document before a start."

to_write = {"type": name, "document": document}
to_write: dict[str, Any] = {"type": name, "document": document}

with open(self.filename, "a") as outfile:
outfile.write(f"{json.dumps(to_write)}\n")
6 changes: 3 additions & 3 deletions src/ibex_bluesky_core/demo_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from ophyd_async.plan_stubs import ensure_connected

from ibex_bluesky_core.devices import get_pv_prefix
from ibex_bluesky_core.devices.block import Block
from ibex_bluesky_core.devices.block import BlockRwRbv, block_rw_rbv
from ibex_bluesky_core.devices.dae import Dae
from ibex_bluesky_core.run_engine import get_run_engine

Expand All @@ -28,12 +28,12 @@ def run_demo_plan() -> None:
"""
RE = get_run_engine()
prefix = get_pv_prefix()
block = Block(prefix, "mot", float)
block = block_rw_rbv(float, "mot")
dae = Dae(prefix)
RE(demo_plan(block, dae), LiveTable(["mot", "DAE"]))


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

Expand Down
Loading