Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions doc/plan_stubs/polling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Continuous polling

[`polling_plan`](ibex_bluesky_core.plans.polling_plan) - is used for moving a motor and dropping updates from a "readable" if no motor updates are provided. An example of this is a laser reading which updates much more quickly than a motor might register it's moved, so the laser readings are not really useful information.

This in itself doesn't start a bluesky "run" so you will need to use a `run_decorator` on any outer plan which calls this plan stub.

2 changes: 0 additions & 2 deletions doc/plans/plans.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ These take "devices" so you can pass your own DAE object and a movable/readable

[`adaptive_scan`](ibex_bluesky_core.plans.adaptive_scan) - this is for an adaptive/relative-adaptive scan.

[`polling_plan`](ibex_bluesky_core.plans.polling_plan) - this is used for moving a motor and dropping updates from a "readable" if no motor updates are provided. An example of this is a laser reading which updates much more quickly than a motor might register it's moved, so the laser readings are not really useful information.

An example of using one of these could be:

```python
Expand Down
52 changes: 50 additions & 2 deletions src/ibex_bluesky_core/plan_stubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

import logging
from collections.abc import Callable, Generator
from typing import ParamSpec, TypeVar, cast
from typing import Any, ParamSpec, TypeVar, cast

import bluesky.plan_stubs as bps
from bluesky import plan_stubs as bps
from bluesky.plan_stubs import trigger_and_read
from bluesky.preprocessors import finalize_wrapper
from bluesky.protocols import Readable
from bluesky.utils import Msg
from ophyd_async.epics.motor import Motor, UseSetMode

from ibex_bluesky_core.devices.reflectometry import ReflParameter
from ibex_bluesky_core.utils import NamedReadableAndMovable

logger = logging.getLogger(__name__)

Expand All @@ -24,6 +27,7 @@
__all__ = [
"call_qt_aware",
"call_sync",
"polling_plan",
"prompt_user_for_choice",
"redefine_motor",
"redefine_refl_parameter",
Expand Down Expand Up @@ -142,3 +146,47 @@ def prompt_user_for_choice(*, prompt: str, choices: list[str]) -> Generator[Msg,
choice = yield from call_sync(input, prompt)

return choice


def polling_plan(
motor: NamedReadableAndMovable, readable: Readable[Any], destination: float
) -> Generator[Msg, None, None]:
"""Move to a destination but drop updates from readable if motor position has not changed.

Note - this does not start a run, this should be done with a run_decorator or similar in an
outer plan which calls this plan.

Args:
motor: the motor to move.
readable: the readable to read updates from, but drop if motor has not moved.
destination: the destination position.

Returns:
None

If we just used bp.scan() with a readable that updates more frequently than a motor can
register that it has moved, we would have lots of updates with the same motor position,
which may not be helpful.

"""
yield from bps.checkpoint()
yield from bps.create()
reading = yield from bps.read(motor)
yield from bps.read(readable)
yield from bps.save()

# start the ramp
status = yield from bps.abs_set(motor, destination, wait=False)
while not status.done:
yield from bps.create()
new_reading = yield from bps.read(motor)
yield from bps.read(readable)

if new_reading[motor.name]["value"] == reading[motor.name]["value"]:
yield from bps.drop()
else:
reading = new_reading
yield from bps.save()

# take a 'post' data point
yield from trigger_and_read([motor, readable])
57 changes: 10 additions & 47 deletions src/ibex_bluesky_core/plans/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
import bluesky.plans as bp
import matplotlib.pyplot as plt
from bluesky import plan_stubs as bps
from bluesky.plan_stubs import trigger_and_read
from bluesky.preprocessors import run_decorator
from bluesky.protocols import NamedMovable, Readable
from bluesky.protocols import NamedMovable
from bluesky.utils import Msg
from matplotlib.axes import Axes
from ophyd_async.plan_stubs import ensure_connected
Expand All @@ -17,13 +15,20 @@
from ibex_bluesky_core.devices.block import BlockWriteConfig, block_rw
from ibex_bluesky_core.devices.simpledae import monitor_normalising_dae
from ibex_bluesky_core.fitting import FitMethod
from ibex_bluesky_core.plan_stubs import call_qt_aware
from ibex_bluesky_core.plan_stubs import call_qt_aware, polling_plan
from ibex_bluesky_core.utils import NamedReadableAndMovable, centred_pixel

if TYPE_CHECKING:
from ibex_bluesky_core.devices.simpledae import SimpleDae

__all__ = ["adaptive_scan", "motor_adaptive_scan", "motor_scan", "polling_plan", "scan"]
__all__ = [
"NamedReadableAndMovable",
"adaptive_scan",
"motor_adaptive_scan",
"motor_scan",
"polling_plan",
"scan",
]


def scan( # noqa: PLR0913
Expand Down Expand Up @@ -296,45 +301,3 @@ def motor_adaptive_scan( # noqa: PLR0913
rel=rel,
)
)


@run_decorator(md={})
def polling_plan(
motor: NamedReadableAndMovable, readable: Readable[Any], destination: float
) -> Generator[Msg, None, None]:
"""Move to a destination but drop updates from readable if motor position has not changed.

Args:
motor: the motor to move.
readable: the readable to read updates from, but drop if motor has not moved.
destination: the destination position.

Returns:
None

If we just used bp.scan() with a readable that updates more frequently than a motor can
register that it has moved, we would have lots of updates with the same motor position,
which may not be helpful.

"""
yield from bps.checkpoint()
yield from bps.create()
reading = yield from bps.read(motor)
yield from bps.read(readable)
yield from bps.save()

# start the ramp
status = yield from bps.abs_set(motor, destination, wait=False)
while not status.done:
yield from bps.create()
new_reading = yield from bps.read(motor)
yield from bps.read(readable)

if new_reading[motor.name]["value"] == reading[motor.name]["value"]:
yield from bps.drop()
else:
reading = new_reading
yield from bps.save()

# take a 'post' data point
yield from trigger_and_read([motor, readable])
12 changes: 9 additions & 3 deletions tests/plans/test_init.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# pyright: reportMissingParameterType=false

from unittest.mock import patch

import pytest
from bluesky.preprocessors import run_decorator
from ophyd_async.plan_stubs import ensure_connected
from ophyd_async.sim import SimMotor
from ophyd_async.testing import callback_on_mock_put, set_mock_value
Expand All @@ -17,11 +17,11 @@
Waiter,
)
from ibex_bluesky_core.fitting import Gaussian
from ibex_bluesky_core.plan_stubs import polling_plan
from ibex_bluesky_core.plans import (
adaptive_scan,
motor_adaptive_scan,
motor_scan,
polling_plan,
scan,
)

Expand Down Expand Up @@ -289,8 +289,14 @@ def lots_of_updates_between_set(*args, **kwargs):
callback_on_mock_put(motor.user_readback, lots_of_updates_between_set)
captured_events = []

@run_decorator(md={})
def p():
return (
yield from polling_plan(motor=motor, readable=block_readable, destination=destination)
) # pyright: ignore[reportArgumentType])

RE(
polling_plan(motor=motor, readable=block_readable, destination=destination), # pyright: ignore[reportArgumentType]
p(),
{"event": lambda x, y: captured_events.append(y["data"])},
)

Expand Down