diff --git a/nslsii/ophyd_async/devices/__init__.py b/nslsii/ophyd_async/devices/__init__.py index 06f47fda..0be90d2b 100644 --- a/nslsii/ophyd_async/devices/__init__.py +++ b/nslsii/ophyd_async/devices/__init__.py @@ -1,4 +1,4 @@ -from rbd9103 import ( +from .rbd9103 import ( RBD9103, RBD9103Range, RBD9103Input, @@ -6,6 +6,13 @@ RBD9103InRangeState, RBD9103Filter, ) +from .eurotherm3k import ( + Eurotherm3k, + Eurotherm3kLoop, + Eurotherm3kControlMode, + Eurotherm3kOnOff, + Eurotherm3kRampRateUnit, +) __all__ = [ "RBD9103", @@ -14,4 +21,9 @@ "RBD9103SamplingMode", "RBD9103InRangeState", "RBD9103Filter", -] + "Eurotherm3k", + "Eurotherm3kLoop", + "Eurotherm3kControlMode", + "Eurotherm3kOnOff", + "Eurotherm3kRampRateUnit", +] \ No newline at end of file diff --git a/nslsii/ophyd_async/devices/eurotherm3k.py b/nslsii/ophyd_async/devices/eurotherm3k.py new file mode 100644 index 00000000..5d92134c --- /dev/null +++ b/nslsii/ophyd_async/devices/eurotherm3k.py @@ -0,0 +1,125 @@ +from dataclasses import dataclass +from functools import cached_property +from typing import Annotated as A + +from ophyd_async.core import ( + MovableLogic, + SignalR, + SignalRW, + StandardMovable, + StandardReadable, + StrictEnum, + TimeoutCalculator, + set_and_wait_for_other_value, + wait_for_value, +) +from ophyd_async.core import StandardReadableFormat as Format +from ophyd_async.epics.core import EpicsDevice, PvSuffix + + +class Eurotherm3kControlMode(StrictEnum): + AUTOMATIC = "Automatic" + MANUAL = "Manual" + + +class Eurotherm3kOnOff(StrictEnum): + OFF = "Off" + ON = "On" + + +class Eurotherm3kRampRateUnit(StrictEnum): + PER_SECOND = "C/s" + PER_MINUTE = "C/min" + + +@dataclass +class _Eurotherm3kLoopLogic(MovableLogic[float]): + loop: "Eurotherm3kLoop" + + async def calculate_timeout(self, old_position: float, new_position: float) -> float: + return self.loop.move_timeout + + async def move(self, new_position: float, timeout: TimeoutCalculator) -> None: + def in_band(value: float) -> bool: + return abs(value - new_position) <= self.loop.tolerance + + def out_of_band(value: float) -> bool: + return not in_band(value) + + await set_and_wait_for_other_value( + self.setpoint, + new_position, + self.readback, + in_band, + timeout=timeout(), + ) + if self.loop.settle_time <= 0: + return + + while True: + settle_time = self.loop.settle_time + if settle_time <= 0: + return + remaining_timeout = timeout() + hold_timeout = ( + settle_time if remaining_timeout is None or remaining_timeout >= settle_time else remaining_timeout + ) + try: + await wait_for_value(self.readback, out_of_band, timeout=hold_timeout) + except TimeoutError: + if remaining_timeout is None or remaining_timeout >= settle_time: + return + raise + await wait_for_value(self.readback, in_band, timeout=timeout()) + + +class Eurotherm3kLoop(EpicsDevice, StandardReadable, StandardMovable[float]): + """One PID control loop of a Eurotherm 3000-series controller. + + Behaves as a settable temperature axis: ``set(T)`` writes the setpoint and + completes once the readback has settled within ``tolerance`` of ``T``. + """ + + # Settle behaviour (override per instance, e.g. ``dev.loop1.tolerance = 0.5``) + tolerance = 1.0 # deg: |readback - setpoint| band counted as "there" + settle_time = 0.0 # s: must stay in band this long (0.0 = first touch) + move_timeout = 3600.0 # s: overall timeout for a move + + setpoint: A[SignalRW[float], PvSuffix.rbv("SP", ":RBV")] + readback: A[SignalR[float], PvSuffix("PV:RBV"), Format.HINTED_SIGNAL] + working_setpoint: A[SignalR[float], PvSuffix("WSP:RBV")] + ramp_rate: A[SignalRW[float], PvSuffix.rbv("RR", ":RBV"), Format.CONFIG_SIGNAL] + ramp_rate_unit: A[SignalR[Eurotherm3kRampRateUnit], PvSuffix("RR:UNIT"), Format.CONFIG_SIGNAL] + p: A[SignalRW[float], PvSuffix.rbv("P", ":RBV"), Format.CONFIG_SIGNAL] + i: A[SignalRW[float], PvSuffix.rbv("I", ":RBV"), Format.CONFIG_SIGNAL] + d: A[SignalRW[float], PvSuffix.rbv("D", ":RBV"), Format.CONFIG_SIGNAL] + control_mode: A[ + SignalRW[Eurotherm3kControlMode], + PvSuffix.rbv("MAN", ":RBV"), + Format.CONFIG_SIGNAL, + ] + autotune: A[SignalRW[Eurotherm3kOnOff], PvSuffix.rbv("AUTOTUNE", ":RBV")] + output: A[SignalRW[float], PvSuffix.rbv("O", ":RBV")] + output_high: A[SignalRW[float], PvSuffix.rbv("OUTPHI", ":RBV"), Format.CONFIG_SIGNAL] + output_low: A[SignalRW[float], PvSuffix.rbv("OUTPLO", ":RBV"), Format.CONFIG_SIGNAL] + loop_break_time: A[SignalRW[float], PvSuffix.rbv("LBT", ":RBV"), Format.CONFIG_SIGNAL] + + @cached_property + def movable_logic(self) -> MovableLogic[float]: + return _Eurotherm3kLoopLogic(setpoint=self.setpoint, readback=self.readback, loop=self) + + +class Eurotherm3k(StandardReadable, EpicsDevice): + """Eurotherm 3000-series controller (nsls2.ioc_deploy eurotherm3k role).""" + + loop1: A[Eurotherm3kLoop, PvSuffix("LOOP1:"), Format.CHILD] + loop2: A[Eurotherm3kLoop, PvSuffix("LOOP2:"), Format.CHILD] + + # --- Global (controller-wide) --- + disable: A[SignalRW[str], PvSuffix("DISABLE"), Format.CONFIG_SIGNAL] + programmer_number: A[SignalRW[int], PvSuffix.rbv("PROGNUM", ":RBV"), Format.CONFIG_SIGNAL] + programmer_run: A[SignalRW[bool], PvSuffix.rbv("PROGRUN", ":RBV")] + programmer_reset: A[SignalRW[bool], PvSuffix("PROGRESET")] + programmer_status: A[SignalR[str], PvSuffix("PROGSTAT:RBV")] + recipe_select: A[SignalRW[str], PvSuffix.rbv("RECSEL", ":RBV"), Format.CONFIG_SIGNAL] + recipe_status: A[SignalR[str], PvSuffix("RECSTAT:RBV")] diff --git a/nslsii/ophyd_async/devices/rbd9103.py b/nslsii/ophyd_async/devices/rbd9103.py index f729fbfd..bc6ab4fc 100644 --- a/nslsii/ophyd_async/devices/rbd9103.py +++ b/nslsii/ophyd_async/devices/rbd9103.py @@ -6,7 +6,7 @@ AsyncStatus, ) from ophyd_async.core import StandardReadableFormat as Format -from ophyd_async.epics.signal import PvSuffix, EpicsDevice +from ophyd_async.epics.core import PvSuffix, EpicsDevice from typing import Annotated as A diff --git a/nslsii/tests/ophyd_async/test_eurotherm3k.py b/nslsii/tests/ophyd_async/test_eurotherm3k.py new file mode 100644 index 00000000..12e47c60 --- /dev/null +++ b/nslsii/tests/ophyd_async/test_eurotherm3k.py @@ -0,0 +1,147 @@ +import asyncio +import time + +import pytest +from ophyd_async.core import ( + LazyMock, + callback_on_mock_put, + get_mock_put, + init_devices, + set_mock_value, +) + +from nslsii.ophyd_async.devices import ( + Eurotherm3k, + Eurotherm3kControlMode, + Eurotherm3kLoop, +) + +PREFIX = "XF:28ID1-ES{ET:05}" + + +@pytest.mark.asyncio +async def test_pvs_are_addressed(): + async with init_devices(mock=True): + dev = Eurotherm3k(PREFIX, name="et") + # child loop signals compose under LOOPn: + assert dev.loop1.setpoint.source == f"mock+ca://{PREFIX}LOOP1:SP:RBV" + assert dev.loop1.readback.source == f"mock+ca://{PREFIX}LOOP1:PV:RBV" + assert dev.loop2.setpoint.source == f"mock+ca://{PREFIX}LOOP2:SP:RBV" + # global signal sits directly under the prefix + assert dev.programmer_status.source == f"mock+ca://{PREFIX}PROGSTAT:RBV" + + +@pytest.mark.asyncio +async def test_format_tags_route_signals(): + async with init_devices(mock=True): + dev = Eurotherm3k(PREFIX, name="et") + reading = await dev.read() + config = await dev.read_configuration() + # HINTED readbacks (merged from both loop CHILDren) appear in read() + assert "et-loop1" in reading + assert "et-loop2" in reading + # CONFIG signals appear in read_configuration() + assert "et-loop1-ramp_rate" in config + + +@pytest.mark.asyncio +async def test_control_mode_enum_roundtrip(): + async with init_devices(mock=True): + dev = Eurotherm3k(PREFIX, name="et") + await dev.loop1.control_mode.set(Eurotherm3kControlMode.MANUAL) + assert await dev.loop1.control_mode.get_value() is Eurotherm3kControlMode.MANUAL + + +@pytest.mark.asyncio +async def test_set_commands_setpoint_and_settles(): + async with init_devices(mock=True): + dev = Eurotherm3k(PREFIX, name="et") + await dev.loop1.set(300.0) + get_mock_put(dev.loop1.setpoint).assert_called_once_with(300.0) + location = await dev.loop1.locate() + assert location["setpoint"] == 300.0 + assert location["readback"] == 300.0 + + +@pytest.mark.asyncio +async def test_set_holds_in_band_for_settle_time(): + """With settle_time > 0, set() waits the hold time even if the readback is + already in band and stops updating -- and must not block until move_timeout.""" + loop = Eurotherm3kLoop(f"{PREFIX}LOOP1:", name="et-loop1") + await loop.connect(mock=LazyMock()) + loop.tolerance = 1.0 + loop.settle_time = 0.2 + loop.move_timeout = 5.0 + # already in band and stable (no further updates arrive) + set_mock_value(loop.readback, 300.0) + start = time.monotonic() + await loop.set(300.0) + elapsed = time.monotonic() - start + # waited ~settle_time, and crucially did NOT hang until move_timeout + assert 0.2 <= elapsed < 5.0 + get_mock_put(loop.setpoint).assert_called_once_with(300.0) + + +@pytest.mark.asyncio +async def test_set_restarts_settle_time_after_out_of_band_excursion(): + loop = Eurotherm3kLoop(f"{PREFIX}LOOP1:", name="et-loop1") + await loop.connect(mock=LazyMock()) + loop.tolerance = 1.0 + loop.settle_time = 0.2 + loop.move_timeout = 5.0 + set_mock_value(loop.readback, 300.0) + + async def make_excursion() -> None: + await asyncio.sleep(loop.settle_time / 2) + set_mock_value(loop.readback, 0.0) + await asyncio.sleep(0) + set_mock_value(loop.readback, 300.0) + + excursion_task: asyncio.Task[None] | None = None + + def start_excursion(value: float) -> None: + nonlocal excursion_task + excursion_task = asyncio.create_task(make_excursion()) + + callback_on_mock_put(loop.setpoint, start_excursion) + start = time.monotonic() + await loop.set(300.0) + elapsed = time.monotonic() - start + assert excursion_task is not None + await excursion_task + assert 0.3 <= elapsed < 5.0 + get_mock_put(loop.setpoint).assert_called_once_with(300.0) + + +@pytest.mark.asyncio +async def test_set_times_out_if_never_in_band(): + loop = Eurotherm3kLoop(f"{PREFIX}LOOP1:", name="et-loop1") + await loop.connect(mock=LazyMock()) + loop.tolerance = 0.5 + loop.move_timeout = 0.3 # keep the test fast + set_mock_value(loop.readback, 0.0) # stays far from 300 + with pytest.raises(TimeoutError): + await loop.set(300.0) + + +# Legacy PDF eurotherm3k (pdf-profile-collection/startup/16-eurotherm_HAB.py): +# each component -> the ophyd-async signal that must address the same PV. +_LEGACY_PV_MAP = { + "setpoint": ("loop1", "setpoint", "LOOP1:SP:RBV"), + "readback": ("loop1", "readback", "LOOP1:PV:RBV"), + "working": ("loop1", "working_setpoint", "LOOP1:WSP:RBV"), + "output": ("loop1", "output", "LOOP1:O:RBV"), + "ramprate": ("loop1", "ramp_rate", "LOOP1:RR:RBV"), + "manual_mode": ("loop1", "control_mode", "LOOP1:MAN:RBV"), + "autotune": ("loop1", "autotune", "LOOP1:AUTOTUNE:RBV"), +} + + +@pytest.mark.asyncio +async def test_legacy_pv_surface_equivalence(): + """Every PV of the legacy PDF eurotherm3k is addressed identically here.""" + async with init_devices(mock=True): + dev = Eurotherm3k(PREFIX, name="et") + for _legacy, (loop, attr, suffix) in _LEGACY_PV_MAP.items(): + signal = getattr(getattr(dev, loop), attr) + assert signal.source == f"mock+ca://{PREFIX}{suffix}" diff --git a/pixi.lock b/pixi.lock index 85af3e65..ec2f05e6 100644 --- a/pixi.lock +++ b/pixi.lock @@ -77,6 +77,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/b9/f44b139096467996b4d85589d9880fd55f0c80a204d298a1b73d0adc2654/scanspec-1.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/01/626bf9585228485b82f0afb4460413f34d2898c8482fab9bfc200be369e1/ophyd_async-0.21.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/89/0265b2b79424ed05b8d1e9c8fca71e1b150478e5b0c19aa50b0ae397326e/velocity_profile-1.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl @@ -86,7 +87,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/83/05/1001c7b589430a45219348c8c32622305304be2173b74d202fd596a39c3e/ophyd_async-0.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl @@ -185,6 +185,7 @@ environments: - pypi: ./ - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl @@ -241,6 +242,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/b9/f44b139096467996b4d85589d9880fd55f0c80a204d298a1b73d0adc2654/scanspec-1.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/01/626bf9585228485b82f0afb4460413f34d2898c8482fab9bfc200be369e1/ophyd_async-0.21.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/89/0265b2b79424ed05b8d1e9c8fca71e1b150478e5b0c19aa50b0ae397326e/velocity_profile-1.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl @@ -258,7 +260,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/83/05/1001c7b589430a45219348c8c32622305304be2173b74d202fd596a39c3e/ophyd_async-0.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/c7/b5c8015d823bfda1a346adb2c634a2101d50bb75d421eb6dcb31acd25ebc/sphinx_rtd_theme-3.1.0-py2.py3-none-any.whl @@ -381,6 +382,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/03/8d/671d81534ea0e2b0e8a121be100020da09eb78861fe3aa8f3ef7dcd3bed1/msgpack-1.2.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl @@ -443,6 +445,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/b9/f44b139096467996b4d85589d9880fd55f0c80a204d298a1b73d0adc2654/scanspec-1.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/01/626bf9585228485b82f0afb4460413f34d2898c8482fab9bfc200be369e1/ophyd_async-0.21.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/89/0265b2b79424ed05b8d1e9c8fca71e1b150478e5b0c19aa50b0ae397326e/velocity_profile-1.0.0-py3-none-any.whl @@ -459,7 +462,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/83/05/1001c7b589430a45219348c8c32622305304be2173b74d202fd596a39c3e/ophyd_async-0.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/c7/b5c8015d823bfda1a346adb2c634a2101d50bb75d421eb6dcb31acd25ebc/sphinx_rtd_theme-3.1.0-py2.py3-none-any.whl @@ -576,6 +578,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/8f/d2f3f532616be4d06c316ef119683e832bd3d41e112bf3a88f4151c95b17/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl @@ -635,6 +638,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/68/b9/f44b139096467996b4d85589d9880fd55f0c80a204d298a1b73d0adc2654/scanspec-1.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/fd/6adabd4f6d5e686f97dd02ce7fce3fe4cf672cbac36b8f67ff4040e8ad8b/msgpack-1.2.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/6c/01/626bf9585228485b82f0afb4460413f34d2898c8482fab9bfc200be369e1/ophyd_async-0.21.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/89/0265b2b79424ed05b8d1e9c8fca71e1b150478e5b0c19aa50b0ae397326e/velocity_profile-1.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl @@ -650,7 +654,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/83/05/1001c7b589430a45219348c8c32622305304be2173b74d202fd596a39c3e/ophyd_async-0.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl @@ -768,6 +771,7 @@ environments: - pypi: ./ - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl @@ -824,6 +828,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/b9/f44b139096467996b4d85589d9880fd55f0c80a204d298a1b73d0adc2654/scanspec-1.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/01/626bf9585228485b82f0afb4460413f34d2898c8482fab9bfc200be369e1/ophyd_async-0.21.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/72/89/0265b2b79424ed05b8d1e9c8fca71e1b150478e5b0c19aa50b0ae397326e/velocity_profile-1.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl @@ -841,7 +846,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/83/05/1001c7b589430a45219348c8c32622305304be2173b74d202fd596a39c3e/ophyd_async-0.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/c7/b5c8015d823bfda1a346adb2c634a2101d50bb75d421eb6dcb31acd25ebc/sphinx_rtd_theme-3.1.0-py2.py3-none-any.whl @@ -1442,7 +1446,7 @@ packages: - numpy - opencv-python - ophyd - - ophyd-async[ca] + - ophyd-async[ca]>=0.21.3 - packaging - pillow - psutil @@ -1478,6 +1482,20 @@ packages: version: 1.2.1 sha256: a28d076ca7c82b9c8728ad90b7147489449557038bed50e4241eb832395169b4 requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl + name: pytest-asyncio + version: 1.4.0 + sha256: 933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1 + requires_dist: + - backports-asyncio-runner>=1.1,<2 ; python_full_version < '3.11' + - pytest>=8.4,<10 + - typing-extensions>=4.12 ; python_full_version < '3.13' + - sphinx>=5.3 ; extra == 'docs' + - sphinx-rtd-theme>=1 ; extra == 'docs' + - sphinx-tabs>=3.5 ; extra == 'docs' + - coverage>=6.2 ; extra == 'testing' + - hypothesis>=5.7.1 ; extra == 'testing' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl name: h11 version: 0.16.0 @@ -3034,6 +3052,30 @@ packages: version: 1.2.1 sha256: 020e881a764b20d8d7ca1a54fc01b8175519d108e3c3f194fddc200bda95951a requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/6c/01/626bf9585228485b82f0afb4460413f34d2898c8482fab9bfc200be369e1/ophyd_async-0.21.3-py3-none-any.whl + name: ophyd-async + version: 0.21.3 + sha256: 63a868c0c64cd2b349983dcce5a46ba9dcea6c922de1f7a3ea92c717f918afd4 + requires_dist: + - numpy + - bluesky>=1.13.1rc2 + - event-model>=1.23 + - pyyaml + - colorlog + - pydantic>=2.0 + - pydantic-numpy + - stamina>=23.1.0 + - scanspec>=1.0.0 + - velocity-profile + - h5py ; extra == 'sim' + - aioca>=2.0a4 ; extra == 'ca' + - p4p>=4.2.0 ; extra == 'pva' + - pytango>=10.1.3 ; extra == 'tango' + - ipython ; extra == 'demo' + - matplotlib ; extra == 'demo' + - pyqt6<6.10 ; extra == 'demo' + - pytest ; extra == 'demo' + requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: pyyaml version: 6.0.3 @@ -3282,30 +3324,6 @@ packages: - markdown-it-py>=2.2.0 - pygments>=2.13.0,<3.0.0 requires_python: '>=3.9.0' -- pypi: https://files.pythonhosted.org/packages/83/05/1001c7b589430a45219348c8c32622305304be2173b74d202fd596a39c3e/ophyd_async-0.20.0-py3-none-any.whl - name: ophyd-async - version: 0.20.0 - sha256: 3459199794587131bd03ee65b75122f6656fd7500d5fbf91cc5ecfd14c889ca3 - requires_dist: - - numpy - - bluesky>=1.13.1rc2 - - event-model>=1.23 - - pyyaml - - colorlog - - pydantic>=2.0 - - pydantic-numpy - - stamina>=23.1.0 - - scanspec>=0.8 - - velocity-profile - - h5py ; extra == 'sim' - - aioca>=2.0a4 ; extra == 'ca' - - p4p>=4.2.0 ; extra == 'pva' - - pytango>=10.1.3 ; extra == 'tango' - - ipython ; extra == 'demo' - - matplotlib ; extra == 'demo' - - pyqt6 ; extra == 'demo' - - pytest ; extra == 'demo' - requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl name: pydantic-settings version: 2.11.0 diff --git a/pyproject.toml b/pyproject.toml index 5d77fe1b..75fdb91c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ dependencies = [ "numpy", "opencv-python", "ophyd", - "ophyd-async[ca]", + "ophyd-async[ca]>=0.21.3", "packaging", "pillow", "psutil",