Skip to content

Commit b667c4c

Browse files
authored
Merge pull request #139 from ISISComputingGroup/redefine
add plan stub to set a motor's current position (redefine)
2 parents b36f8f1 + 5bfccaf commit b667c4c

File tree

5 files changed

+132
-4
lines changed

5 files changed

+132
-4
lines changed

doc/plan_stubs/redefining.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Redefinition
2+
3+
The plan stubs in this module implement 'redefinition' of a parameter, where supported. That is, no physical movement
4+
occurs, but the reported position changes to the given position.
5+
6+
## `redefine_motor`
7+
8+
The {py:obj}`ibex_bluesky_core.plan_stubs.redefine_motor` plan stub can be used to redefine the current
9+
position of a motor (for example a {py:obj}`ibex_bluesky_core.devices.block.BlockMot`) to a new value.
10+
11+
The motor does not physically move, but after this plan stub executes, the current position will be defined
12+
as `value`.
13+
14+
```python
15+
from ibex_bluesky_core.plan_stubs import redefine_motor
16+
from ibex_bluesky_core.devices.block import block_mot, BlockMot
17+
import bluesky.plan_stubs as bps
18+
19+
20+
def my_plan():
21+
motor: BlockMot = block_mot("my_motor")
22+
optimimum_value: float = ...
23+
24+
# Physically move the motor to it's optimum position
25+
yield from bps.mv(motor, optimimum_value)
26+
27+
# Redefine the current position as zero
28+
yield from redefine_motor(motor, 0.)
29+
```
30+
31+
## `redefine_refl_parameter`
32+
33+
The {py:obj}`ibex_bluesky_core.plan_stubs.redefine_refl_parameter` plan stub can be used to redefine the current
34+
position of a {py:obj}`ibex_bluesky_core.devices.reflectometry.ReflParameter` to a new value.
35+
36+
This plan stub has an identical API to that of the {py:obj}`ibex_bluesky_core.plan_stubs.redefine_motor` plan stub
37+
described above, but operates on a reflectometry parameter rather than a motor.

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ classifiers = [
4242

4343
dependencies = [
4444
"bluesky", # Bluesky framework
45-
"ophyd-async[ca] >= 0.8.0", # Device abstraction
45+
# Pinned for extra motor fields
46+
# Unpin when a version >= 0.9.1 is available
47+
"ophyd-async[ca,pva] @ git+https://github.com/bluesky/ophyd-async@bf6b71f045bf297e2bbbfd3ea4de7f368643a948", # Device abstraction
4648
"matplotlib", # Plotting
4749
"lmfit", # Fitting
4850
"scipy", # Definitions of erf/erfc functions

src/ibex_bluesky_core/plan_stubs/__init__.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
"""Core plan stubs."""
22

3+
import logging
34
from collections.abc import Callable, Generator
45
from typing import ParamSpec, TypeVar, cast
56

67
import bluesky.plan_stubs as bps
8+
from bluesky.preprocessors import finalize_wrapper
79
from bluesky.utils import Msg
10+
from ophyd_async.epics.motor import Motor, UseSetMode
11+
12+
from ibex_bluesky_core.devices.reflectometry import ReflParameter
13+
14+
logger = logging.getLogger(__name__)
815

916
P = ParamSpec("P")
1017
T = TypeVar("T")
@@ -14,7 +21,13 @@
1421
CALL_QT_AWARE_MSG_KEY = "ibex_bluesky_core_call_qt_aware"
1522

1623

17-
__all__ = ["call_qt_aware", "call_sync", "prompt_user_for_choice"]
24+
__all__ = [
25+
"call_qt_aware",
26+
"call_sync",
27+
"prompt_user_for_choice",
28+
"redefine_motor",
29+
"redefine_refl_parameter",
30+
]
1831

1932

2033
def call_sync(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> Generator[Msg, None, T]:
@@ -79,6 +92,46 @@ def call_qt_aware(
7992
return cast(T, (yield Msg(CALL_QT_AWARE_MSG_KEY, func, *args, **kwargs)))
8093

8194

95+
def redefine_motor(motor: Motor, position: float) -> Generator[Msg, None, None]:
96+
"""Redefines the current positions of a motor.
97+
98+
Note:
99+
This does not move the motor, it just redefines its position to be the given value.
100+
101+
Args:
102+
motor: The motor to set a position on.
103+
position: The position to set.
104+
105+
"""
106+
logger.info("Redefining motor %s to %s", motor.name, position)
107+
108+
def make_motor_usable() -> Generator[Msg, None, None]:
109+
yield from bps.abs_set(motor.set_use_switch, UseSetMode.USE)
110+
111+
def inner() -> Generator[Msg, None, None]:
112+
yield from bps.abs_set(motor.set_use_switch, UseSetMode.SET)
113+
yield from bps.abs_set(motor.user_setpoint, position)
114+
115+
return (yield from finalize_wrapper(inner(), make_motor_usable()))
116+
117+
118+
def redefine_refl_parameter(
119+
parameter: ReflParameter, position: float
120+
) -> Generator[Msg, None, None]:
121+
"""Redefines the current positions of a reflectometry parameter.
122+
123+
Note:
124+
This does not move the parameter, it just redefines its position to be the given value.
125+
126+
Args:
127+
parameter: The reflectometry parameter to set a position on.
128+
position: The position to set.
129+
130+
"""
131+
logger.info("Redefining refl parameter %s to %s", parameter.name, position)
132+
yield from bps.mv(parameter.redefine, position)
133+
134+
82135
def prompt_user_for_choice(*, prompt: str, choices: list[str]) -> Generator[Msg, None, str]:
83136
"""Prompt the user to choose between a limited set of options.
84137

tests/plans/test_init.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ async def test_polling_plan_drops_readable_updates_if_no_new_motor_position(RE):
271271
motor.user_readback.set_name("motor1")
272272
await motor.velocity.set(2)
273273
block_readable = BlockR(prefix="UNITTEST:", block_name="READABLE", datatype=int)
274-
initial_pos = 0
274+
initial_pos = 0.1
275275
destination = 2
276276
initial_reading = 10
277277
RE(ensure_connected(motor, block_readable, mock=True))

tests/test_plan_stubs.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
# pyright: reportMissingParameterType=false
22
import time
33
from asyncio import CancelledError
4-
from unittest.mock import MagicMock, patch
4+
from unittest.mock import MagicMock, call, patch
55

66
import matplotlib.pyplot as plt
77
import pytest
88
from bluesky.utils import Msg
9+
from ophyd_async.epics.motor import UseSetMode
10+
from ophyd_async.plan_stubs import ensure_connected
11+
from ophyd_async.testing import callback_on_mock_put, get_mock_put, set_mock_value
912

13+
from ibex_bluesky_core.devices.block import BlockMot
14+
from ibex_bluesky_core.devices.reflectometry import ReflParameter
1015
from ibex_bluesky_core.plan_stubs import (
1116
CALL_QT_AWARE_MSG_KEY,
1217
call_qt_aware,
1318
call_sync,
1419
prompt_user_for_choice,
20+
redefine_motor,
21+
redefine_refl_parameter,
1522
)
1623
from ibex_bluesky_core.run_engine._msg_handlers import call_sync_handler
1724

@@ -126,6 +133,35 @@ def plan():
126133
mock.assert_not_called()
127134

128135

136+
def test_redefine_motor(RE):
137+
motor = BlockMot(prefix="", block_name="some_motor")
138+
139+
def plan():
140+
yield from ensure_connected(motor, mock=True)
141+
yield from redefine_motor(motor, 42.0)
142+
143+
RE(plan())
144+
145+
get_mock_put(motor.set_use_switch).assert_has_calls(
146+
[call(UseSetMode.SET, wait=True), call(UseSetMode.USE, wait=True)]
147+
)
148+
149+
get_mock_put(motor.user_setpoint).assert_called_once_with(42.0, wait=True)
150+
151+
152+
async def test_redefine_refl_parameter(RE):
153+
param = ReflParameter(prefix="", name="some_refl_parameter", changing_timeout_s=60)
154+
await param.connect(mock=True)
155+
156+
callback_on_mock_put(
157+
param.redefine.define_pos_sp, lambda *a, **k: set_mock_value(param.redefine.changed, True)
158+
)
159+
160+
RE(redefine_refl_parameter(param, 42.0))
161+
162+
get_mock_put(param.redefine.define_pos_sp).assert_called_once_with(42.0, wait=True)
163+
164+
129165
def test_get_user_input(RE):
130166
with patch("ibex_bluesky_core.plan_stubs.input") as mock_input:
131167
mock_input.__name__ = "mock"

0 commit comments

Comments
 (0)