From db984bbe36dac9730dd46791367435b9da641b55 Mon Sep 17 00:00:00 2001 From: James Souter Date: Mon, 15 Apr 2024 15:57:26 +0100 Subject: [PATCH 1/6] Add create_soft_signal methods to signal --- src/ophyd_async/core/__init__.py | 4 ++++ src/ophyd_async/core/signal.py | 14 +++++++++++++- tests/core/test_signal.py | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/ophyd_async/core/__init__.py b/src/ophyd_async/core/__init__.py index 103638019d..efeff77fa8 100644 --- a/src/ophyd_async/core/__init__.py +++ b/src/ophyd_async/core/__init__.py @@ -30,6 +30,8 @@ SignalRW, SignalW, SignalX, + create_soft_signal_r, + create_soft_signal_rw, observe_value, set_and_wait_for_value, set_sim_callback, @@ -67,6 +69,8 @@ "SignalW", "SignalRW", "SignalX", + "create_soft_signal_r", + "create_soft_signal_rw", "observe_value", "set_and_wait_for_value", "set_sim_callback", diff --git a/src/ophyd_async/core/signal.py b/src/ophyd_async/core/signal.py index 927faaf402..6397d8a0f3 100644 --- a/src/ophyd_async/core/signal.py +++ b/src/ophyd_async/core/signal.py @@ -2,7 +2,7 @@ import asyncio import functools -from typing import AsyncGenerator, Callable, Dict, Generic, Optional, Union +from typing import AsyncGenerator, Callable, Dict, Generic, Optional, Type, Union from bluesky.protocols import ( Descriptor, @@ -253,6 +253,18 @@ def set_sim_callback(signal: Signal[T], callback: ReadingValueCallback[T]) -> No return _sim_backends[signal].set_callback(callback) +def create_soft_signal_rw( + datatype: Optional[Type[T]], name: str, source_prefix: str +) -> SignalRW[T]: + return SignalRW(SimSignalBackend(datatype, f"sim://{source_prefix}:{name}")) + + +def create_soft_signal_r( + datatype: Optional[Type[T]], name: str, source_prefix: str +) -> SignalR[T]: + return SignalR(SimSignalBackend(datatype, f"sim://{source_prefix}:{name}")) + + async def observe_value(signal: SignalR[T], timeout=None) -> AsyncGenerator[T, None]: """Subscribe to the value of a signal so it can be iterated from. diff --git a/tests/core/test_signal.py b/tests/core/test_signal.py index 5393cb1a78..dfc0ba1ed5 100644 --- a/tests/core/test_signal.py +++ b/tests/core/test_signal.py @@ -6,8 +6,11 @@ from ophyd_async.core import ( Signal, + SignalR, SignalRW, SimSignalBackend, + create_soft_signal_r, + create_soft_signal_rw, set_and_wait_for_value, set_sim_put_proceeds, set_sim_value, @@ -119,3 +122,18 @@ async def test_set_and_wait_for_value(): assert not st.done set_sim_put_proceeds(sim_signal, True) assert await time_taken_by(st) < 0.1 + + +async def test_create_soft_signal(): + TEST_PREFIX = "TEST-PREFIX" + ro_signal = create_soft_signal_r(str, "RO-SIGNAL", TEST_PREFIX) + assert isinstance(ro_signal, SignalR) + assert isinstance(ro_signal._backend, SimSignalBackend) + rw_signal = create_soft_signal_rw(str, "RW-SIGNAL", TEST_PREFIX) + assert isinstance(rw_signal, SignalRW) + assert isinstance(ro_signal._backend, SimSignalBackend) + await ro_signal.connect() + await rw_signal.connect() + # connecting with sim=False uses existing SimSignalBackend + assert ro_signal._backend is ro_signal._init_backend + assert rw_signal._backend is rw_signal._init_backend From 10ecc63ea61e3fa0a76396967a7c266cedf8b1e5 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 16 Apr 2024 08:44:55 +0000 Subject: [PATCH 2/6] rename to soft_signal_r/rw and return backend with soft_signal_r --- src/ophyd_async/core/__init__.py | 8 ++++---- src/ophyd_async/core/signal.py | 17 ++++++++++++----- tests/core/test_signal.py | 31 ++++++++++++++++++------------- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/ophyd_async/core/__init__.py b/src/ophyd_async/core/__init__.py index efeff77fa8..8449121523 100644 --- a/src/ophyd_async/core/__init__.py +++ b/src/ophyd_async/core/__init__.py @@ -30,13 +30,13 @@ SignalRW, SignalW, SignalX, - create_soft_signal_r, - create_soft_signal_rw, observe_value, set_and_wait_for_value, set_sim_callback, set_sim_put_proceeds, set_sim_value, + soft_signal_r, + soft_signal_rw, wait_for_value, ) from .signal_backend import SignalBackend @@ -69,8 +69,8 @@ "SignalW", "SignalRW", "SignalX", - "create_soft_signal_r", - "create_soft_signal_rw", + "soft_signal_r", + "soft_signal_rw", "observe_value", "set_and_wait_for_value", "set_sim_callback", diff --git a/src/ophyd_async/core/signal.py b/src/ophyd_async/core/signal.py index 6397d8a0f3..8f0e9eebb1 100644 --- a/src/ophyd_async/core/signal.py +++ b/src/ophyd_async/core/signal.py @@ -2,7 +2,7 @@ import asyncio import functools -from typing import AsyncGenerator, Callable, Dict, Generic, Optional, Type, Union +from typing import AsyncGenerator, Callable, Dict, Generic, Optional, Tuple, Type, Union from bluesky.protocols import ( Descriptor, @@ -253,16 +253,23 @@ def set_sim_callback(signal: Signal[T], callback: ReadingValueCallback[T]) -> No return _sim_backends[signal].set_callback(callback) -def create_soft_signal_rw( +def soft_signal_rw( datatype: Optional[Type[T]], name: str, source_prefix: str ) -> SignalRW[T]: + """Creates a read-writable Signal with a SimSignalBackend""" return SignalRW(SimSignalBackend(datatype, f"sim://{source_prefix}:{name}")) -def create_soft_signal_r( +def soft_signal_r( datatype: Optional[Type[T]], name: str, source_prefix: str -) -> SignalR[T]: - return SignalR(SimSignalBackend(datatype, f"sim://{source_prefix}:{name}")) +) -> Tuple[SignalR[T], SimSignalBackend]: + """Returns a tuple of a read-only Signal and its SimSignalBackend through + which the signal can be internally modified within the device. Use + soft_signal_rw if you want a device that is externally modifiable + """ + backend = SimSignalBackend(datatype, f"sim://{source_prefix}:{name}") + signal = SignalR(backend) + return (signal, backend) async def observe_value(signal: SignalR[T], timeout=None) -> AsyncGenerator[T, None]: diff --git a/tests/core/test_signal.py b/tests/core/test_signal.py index dfc0ba1ed5..8bc161d592 100644 --- a/tests/core/test_signal.py +++ b/tests/core/test_signal.py @@ -9,11 +9,11 @@ SignalR, SignalRW, SimSignalBackend, - create_soft_signal_r, - create_soft_signal_rw, set_and_wait_for_value, set_sim_put_proceeds, set_sim_value, + soft_signal_r, + soft_signal_rw, wait_for_value, ) from ophyd_async.core.utils import DEFAULT_TIMEOUT @@ -124,16 +124,21 @@ async def test_set_and_wait_for_value(): assert await time_taken_by(st) < 0.1 -async def test_create_soft_signal(): +@pytest.mark.parametrize( + "signal_method,signal_class", + [(soft_signal_r, SignalR), (soft_signal_rw, SignalRW)], +) +async def test_create_soft_signal(signal_method, signal_class): TEST_PREFIX = "TEST-PREFIX" - ro_signal = create_soft_signal_r(str, "RO-SIGNAL", TEST_PREFIX) - assert isinstance(ro_signal, SignalR) - assert isinstance(ro_signal._backend, SimSignalBackend) - rw_signal = create_soft_signal_rw(str, "RW-SIGNAL", TEST_PREFIX) - assert isinstance(rw_signal, SignalRW) - assert isinstance(ro_signal._backend, SimSignalBackend) - await ro_signal.connect() - await rw_signal.connect() + SIGNAL_NAME = "SIGNAL" + if signal_method == soft_signal_r: + signal, backend = signal_method(str, SIGNAL_NAME, TEST_PREFIX) + elif signal_method == soft_signal_rw: + signal = signal_method(str, SIGNAL_NAME, TEST_PREFIX) + backend = signal._backend + assert signal._backend.source == f"sim://{TEST_PREFIX}:{SIGNAL_NAME}" + assert isinstance(signal, signal_class) + assert isinstance(signal._backend, SimSignalBackend) + await signal.connect() # connecting with sim=False uses existing SimSignalBackend - assert ro_signal._backend is ro_signal._init_backend - assert rw_signal._backend is rw_signal._init_backend + assert signal._backend is backend From d8331fee63a8bddeb7da9e1b2f4c53aa32b312e8 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 16 Apr 2024 12:09:21 +0000 Subject: [PATCH 3/6] Rename soft_signal_r to soft_signal_r_and_backend --- src/ophyd_async/core/__init__.py | 4 ++-- src/ophyd_async/core/signal.py | 2 +- tests/core/test_signal.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ophyd_async/core/__init__.py b/src/ophyd_async/core/__init__.py index 8449121523..7381415b72 100644 --- a/src/ophyd_async/core/__init__.py +++ b/src/ophyd_async/core/__init__.py @@ -35,7 +35,7 @@ set_sim_callback, set_sim_put_proceeds, set_sim_value, - soft_signal_r, + soft_signal_r_and_backend, soft_signal_rw, wait_for_value, ) @@ -69,7 +69,7 @@ "SignalW", "SignalRW", "SignalX", - "soft_signal_r", + "soft_signal_r_and_backend", "soft_signal_rw", "observe_value", "set_and_wait_for_value", diff --git a/src/ophyd_async/core/signal.py b/src/ophyd_async/core/signal.py index 8f0e9eebb1..49db513a20 100644 --- a/src/ophyd_async/core/signal.py +++ b/src/ophyd_async/core/signal.py @@ -260,7 +260,7 @@ def soft_signal_rw( return SignalRW(SimSignalBackend(datatype, f"sim://{source_prefix}:{name}")) -def soft_signal_r( +def soft_signal_r_and_backend( datatype: Optional[Type[T]], name: str, source_prefix: str ) -> Tuple[SignalR[T], SimSignalBackend]: """Returns a tuple of a read-only Signal and its SimSignalBackend through diff --git a/tests/core/test_signal.py b/tests/core/test_signal.py index 8bc161d592..ff2f228bd9 100644 --- a/tests/core/test_signal.py +++ b/tests/core/test_signal.py @@ -12,7 +12,7 @@ set_and_wait_for_value, set_sim_put_proceeds, set_sim_value, - soft_signal_r, + soft_signal_r_and_backend, soft_signal_rw, wait_for_value, ) @@ -126,12 +126,12 @@ async def test_set_and_wait_for_value(): @pytest.mark.parametrize( "signal_method,signal_class", - [(soft_signal_r, SignalR), (soft_signal_rw, SignalRW)], + [(soft_signal_r_and_backend, SignalR), (soft_signal_rw, SignalRW)], ) async def test_create_soft_signal(signal_method, signal_class): TEST_PREFIX = "TEST-PREFIX" SIGNAL_NAME = "SIGNAL" - if signal_method == soft_signal_r: + if signal_method == soft_signal_r_and_backend: signal, backend = signal_method(str, SIGNAL_NAME, TEST_PREFIX) elif signal_method == soft_signal_rw: signal = signal_method(str, SIGNAL_NAME, TEST_PREFIX) From 4f39e59ef1a0c362bc3b5e133afe8400196c151d Mon Sep 17 00:00:00 2001 From: James Souter Date: Wed, 17 Apr 2024 13:27:38 +0000 Subject: [PATCH 4/6] Allow soft_signal methods to set initial value of SimSignalBackend --- src/ophyd_async/core/signal.py | 16 ++++++++++++---- src/ophyd_async/core/sim_signal_backend.py | 16 +++++++++++++--- tests/core/test_signal.py | 6 ++++-- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/ophyd_async/core/signal.py b/src/ophyd_async/core/signal.py index 49db513a20..2e471a4ba1 100644 --- a/src/ophyd_async/core/signal.py +++ b/src/ophyd_async/core/signal.py @@ -254,20 +254,28 @@ def set_sim_callback(signal: Signal[T], callback: ReadingValueCallback[T]) -> No def soft_signal_rw( - datatype: Optional[Type[T]], name: str, source_prefix: str + datatype: Optional[Type[T]], + name: str, + source_prefix: str, + initial_value: Optional[T] = None, ) -> SignalRW[T]: """Creates a read-writable Signal with a SimSignalBackend""" - return SignalRW(SimSignalBackend(datatype, f"sim://{source_prefix}:{name}")) + return SignalRW( + SimSignalBackend(datatype, f"sim://{source_prefix}:{name}", initial_value) + ) def soft_signal_r_and_backend( - datatype: Optional[Type[T]], name: str, source_prefix: str + datatype: Optional[Type[T]], + name: str, + source_prefix: str, + initial_value: Optional[T] = None, ) -> Tuple[SignalR[T], SimSignalBackend]: """Returns a tuple of a read-only Signal and its SimSignalBackend through which the signal can be internally modified within the device. Use soft_signal_rw if you want a device that is externally modifiable """ - backend = SimSignalBackend(datatype, f"sim://{source_prefix}:{name}") + backend = SimSignalBackend(datatype, f"sim://{source_prefix}:{name}", initial_value) signal = SignalR(backend) return (signal, backend) diff --git a/src/ophyd_async/core/sim_signal_backend.py b/src/ophyd_async/core/sim_signal_backend.py index b0190344bb..9ea3f61853 100644 --- a/src/ophyd_async/core/sim_signal_backend.py +++ b/src/ophyd_async/core/sim_signal_backend.py @@ -107,23 +107,33 @@ class SimSignalBackend(SignalBackend[T]): """An simulated backend to a Signal, created with ``Signal.connect(sim=True)``""" _value: T - _initial_value: T + _initial_value: Optional[T] _timestamp: float _severity: int - def __init__(self, datatype: Optional[Type[T]], source: str) -> None: + def __init__( + self, + datatype: Optional[Type[T]], + source: str, + initial_value: Optional[T] = None, + ) -> None: pv = re.split(r"://", source)[-1] self.source = f"sim://{pv}" self.datatype = datatype self.pv = source self.converter: SimConverter = DisconnectedSimConverter() + self._initial_value = initial_value self.put_proceeds = asyncio.Event() self.put_proceeds.set() self.callback: Optional[ReadingValueCallback[T]] = None async def connect(self, timeout: float = DEFAULT_TIMEOUT) -> None: self.converter = make_converter(self.datatype) - self._initial_value = self.converter.make_initial_value(self.datatype) + if self._initial_value is None: + self._initial_value = self.converter.make_initial_value(self.datatype) + else: + # convert potentially unconverted initial value passed to init method + self._initial_value = self.converter.write_value(self._initial_value) self._severity = 0 await self.put(None) diff --git a/tests/core/test_signal.py b/tests/core/test_signal.py index ff2f228bd9..3b16601ddd 100644 --- a/tests/core/test_signal.py +++ b/tests/core/test_signal.py @@ -131,14 +131,16 @@ async def test_set_and_wait_for_value(): async def test_create_soft_signal(signal_method, signal_class): TEST_PREFIX = "TEST-PREFIX" SIGNAL_NAME = "SIGNAL" + INITIAL_VALUE = "INITIAL" if signal_method == soft_signal_r_and_backend: - signal, backend = signal_method(str, SIGNAL_NAME, TEST_PREFIX) + signal, backend = signal_method(str, SIGNAL_NAME, TEST_PREFIX, INITIAL_VALUE) elif signal_method == soft_signal_rw: - signal = signal_method(str, SIGNAL_NAME, TEST_PREFIX) + signal = signal_method(str, SIGNAL_NAME, TEST_PREFIX, INITIAL_VALUE) backend = signal._backend assert signal._backend.source == f"sim://{TEST_PREFIX}:{SIGNAL_NAME}" assert isinstance(signal, signal_class) assert isinstance(signal._backend, SimSignalBackend) await signal.connect() + assert (await signal.get_value()) == INITIAL_VALUE # connecting with sim=False uses existing SimSignalBackend assert signal._backend is backend From f78780d87e7c810dc519ca31b0e354fcc1730dee Mon Sep 17 00:00:00 2001 From: James Souter Date: Thu, 18 Apr 2024 14:24:41 +0100 Subject: [PATCH 5/6] Make backend source a method. signals can pass a value to the backend, signal sources are now properties Can no longer pass source to SimSignalBackend at init --- src/ophyd_async/core/signal.py | 17 +++++++++-------- src/ophyd_async/core/signal_backend.py | 7 +++++-- src/ophyd_async/core/sim_signal_backend.py | 12 +++++------- src/ophyd_async/epics/_backend/_aioca.py | 8 +++++--- src/ophyd_async/epics/_backend/_p4p.py | 9 ++++++--- src/ophyd_async/epics/pvi/pvi.py | 6 +++--- src/ophyd_async/sim/pattern_generator.py | 4 ++-- tests/core/test_flyer.py | 4 ++-- tests/core/test_signal.py | 19 +++++++++---------- tests/core/test_sim.py | 15 ++++++++------- tests/core/test_utils.py | 2 +- .../areadetector/test_single_trigger_det.py | 4 ---- tests/epics/areadetector/test_writers.py | 2 +- tests/epics/demo/test_demo.py | 15 +++------------ tests/epics/demo/test_demo_ad_sim_detector.py | 5 ++--- tests/epics/motion/test_motor.py | 6 ------ tests/epics/test_signals.py | 19 +++++++++++-------- tests/panda/test_writer.py | 10 +++++----- tests/sim/test_sim_writer.py | 4 ++-- tests/test_flyer_with_panda.py | 4 ++-- 20 files changed, 81 insertions(+), 91 deletions(-) diff --git a/src/ophyd_async/core/signal.py b/src/ophyd_async/core/signal.py index 2e471a4ba1..a9c76af3f4 100644 --- a/src/ophyd_async/core/signal.py +++ b/src/ophyd_async/core/signal.py @@ -61,7 +61,7 @@ def set_name(self, name: str = ""): async def connect(self, sim=False, timeout=DEFAULT_TIMEOUT): if sim: self._backend = SimSignalBackend( - datatype=self._init_backend.datatype, source=self._init_backend.source + datatype=self._init_backend.datatype ) _sim_backends[self] = self._backend else: @@ -72,7 +72,7 @@ async def connect(self, sim=False, timeout=DEFAULT_TIMEOUT): @property def source(self) -> str: """Like ca://PV_PREFIX:SIGNAL, or "" if not set""" - return self._backend.source + return self._backend.source(self.name) __lt__ = __le__ = __eq__ = __ge__ = __gt__ = __ne__ = _fail @@ -168,7 +168,7 @@ async def read(self, cached: Optional[bool] = None) -> Dict[str, Reading]: @_add_timeout async def describe(self) -> Dict[str, Descriptor]: """Return a single item dict with the descriptor in it""" - return {self.name: await self._backend.get_descriptor()} + return {self.name: await self._backend.get_descriptor(self.source)} @_add_timeout async def get_value(self, cached: Optional[bool] = None) -> T: @@ -256,27 +256,28 @@ def set_sim_callback(signal: Signal[T], callback: ReadingValueCallback[T]) -> No def soft_signal_rw( datatype: Optional[Type[T]], name: str, - source_prefix: str, initial_value: Optional[T] = None, ) -> SignalRW[T]: """Creates a read-writable Signal with a SimSignalBackend""" - return SignalRW( - SimSignalBackend(datatype, f"sim://{source_prefix}:{name}", initial_value) + signal = SignalRW( + SimSignalBackend(datatype, initial_value) ) + signal.set_name(name) + return signal def soft_signal_r_and_backend( datatype: Optional[Type[T]], name: str, - source_prefix: str, initial_value: Optional[T] = None, ) -> Tuple[SignalR[T], SimSignalBackend]: """Returns a tuple of a read-only Signal and its SimSignalBackend through which the signal can be internally modified within the device. Use soft_signal_rw if you want a device that is externally modifiable """ - backend = SimSignalBackend(datatype, f"sim://{source_prefix}:{name}", initial_value) + backend = SimSignalBackend(datatype, initial_value) signal = SignalR(backend) + signal.set_name(name) return (signal, backend) diff --git a/src/ophyd_async/core/signal_backend.py b/src/ophyd_async/core/signal_backend.py index 778e62c76c..95d0ca1768 100644 --- a/src/ophyd_async/core/signal_backend.py +++ b/src/ophyd_async/core/signal_backend.py @@ -13,7 +13,10 @@ class SignalBackend(Generic[T]): datatype: Optional[Type[T]] = None #: Like ca://PV_PREFIX:SIGNAL - source: str = "" + @abstractmethod + def source(name: str) -> str: + """Return source of signal. Signals may pass a name to the backend, which can be + used or discarded.""" @abstractmethod async def connect(self, timeout: float = DEFAULT_TIMEOUT): @@ -24,7 +27,7 @@ async def put(self, value: Optional[T], wait=True, timeout=None): """Put a value to the PV, if wait then wait for completion for up to timeout""" @abstractmethod - async def get_descriptor(self) -> Descriptor: + async def get_descriptor(self, source: str) -> Descriptor: """Metadata like source, dtype, shape, precision, units""" @abstractmethod diff --git a/src/ophyd_async/core/sim_signal_backend.py b/src/ophyd_async/core/sim_signal_backend.py index 9ea3f61853..267caaa612 100644 --- a/src/ophyd_async/core/sim_signal_backend.py +++ b/src/ophyd_async/core/sim_signal_backend.py @@ -2,7 +2,6 @@ import asyncio import inspect -import re import time from collections import abc from dataclasses import dataclass @@ -114,19 +113,18 @@ class SimSignalBackend(SignalBackend[T]): def __init__( self, datatype: Optional[Type[T]], - source: str, initial_value: Optional[T] = None, ) -> None: - pv = re.split(r"://", source)[-1] - self.source = f"sim://{pv}" self.datatype = datatype - self.pv = source self.converter: SimConverter = DisconnectedSimConverter() self._initial_value = initial_value self.put_proceeds = asyncio.Event() self.put_proceeds.set() self.callback: Optional[ReadingValueCallback[T]] = None + def source(self, name: str) -> str: + return f"soft://{name}" + async def connect(self, timeout: float = DEFAULT_TIMEOUT) -> None: self.converter = make_converter(self.datatype) if self._initial_value is None: @@ -160,8 +158,8 @@ def _set_value(self, value: T): if self.callback: self.callback(reading, self._value) - async def get_descriptor(self) -> Descriptor: - return self.converter.descriptor(self.source, self._value) + async def get_descriptor(self, source: str) -> Descriptor: + return self.converter.descriptor(source, self._value) async def get_reading(self) -> Reading: return self.converter.reading(self._value, self._timestamp, self._severity) diff --git a/src/ophyd_async/epics/_backend/_aioca.py b/src/ophyd_async/epics/_backend/_aioca.py index db8180641b..f39a09d337 100644 --- a/src/ophyd_async/epics/_backend/_aioca.py +++ b/src/ophyd_async/epics/_backend/_aioca.py @@ -170,9 +170,11 @@ def __init__(self, datatype: Optional[Type[T]], read_pv: str, write_pv: str): self.write_pv = write_pv self.initial_values: Dict[str, AugmentedValue] = {} self.converter: CaConverter = DisconnectedCaConverter(None, None) - self.source = f"ca://{self.read_pv}" self.subscription: Optional[Subscription] = None + def source(self, name: str): + return f"ca://{self.read_pv}" + async def _store_initial_value(self, pv, timeout: float = DEFAULT_TIMEOUT): try: self.initial_values[pv] = await caget( @@ -216,9 +218,9 @@ async def _caget(self, format: Format) -> AugmentedValue: timeout=None, ) - async def get_descriptor(self) -> Descriptor: + async def get_descriptor(self, source: str) -> Descriptor: value = await self._caget(FORMAT_CTRL) - return self.converter.descriptor(self.source, value) + return self.converter.descriptor(source, value) async def get_reading(self) -> Reading: value = await self._caget(FORMAT_TIME) diff --git a/src/ophyd_async/epics/_backend/_p4p.py b/src/ophyd_async/epics/_backend/_p4p.py index 759d86b7bb..586d4cce9b 100644 --- a/src/ophyd_async/epics/_backend/_p4p.py +++ b/src/ophyd_async/epics/_backend/_p4p.py @@ -236,9 +236,12 @@ def __init__(self, datatype: Optional[Type[T]], read_pv: str, write_pv: str): self.write_pv = write_pv self.initial_values: Dict[str, Any] = {} self.converter: PvaConverter = DisconnectedPvaConverter() - self.source = f"pva://{self.read_pv}" self.subscription: Optional[Subscription] = None + @property + def source(self, name: str): + return f"pva://{self.read_pv}" + @property def ctxt(self) -> Context: if PvaSignalBackend._ctxt is None: @@ -290,9 +293,9 @@ async def put(self, value: Optional[T], wait=True, timeout=None): ) raise NotConnected(f"pva://{self.write_pv}") from exc - async def get_descriptor(self) -> Descriptor: + async def get_descriptor(self, source: str) -> Descriptor: value = await self.ctxt.get(self.read_pv) - return self.converter.descriptor(self.source, value) + return self.converter.descriptor(source, value) def _pva_request_string(self, fields: List[str]) -> str: """ diff --git a/src/ophyd_async/epics/pvi/pvi.py b/src/ophyd_async/epics/pvi/pvi.py index ea20656261..881d4f6b24 100644 --- a/src/ophyd_async/epics/pvi/pvi.py +++ b/src/ophyd_async/epics/pvi/pvi.py @@ -172,8 +172,8 @@ def _sim_common_blocks(device: Device, stripped_type: Optional[Type] = None): if is_device_vector: if is_signal: - sub_device_1 = device_cls(SimSignalBackend(signal_dtype, device_name)) - sub_device_2 = device_cls(SimSignalBackend(signal_dtype, device_name)) + sub_device_1 = device_cls(SimSignalBackend(signal_dtype)) + sub_device_2 = device_cls(SimSignalBackend(signal_dtype)) sub_device = DeviceVector({1: sub_device_1, 2: sub_device_2}) else: sub_device = DeviceVector({1: device_cls(), 2: device_cls()}) @@ -185,7 +185,7 @@ def _sim_common_blocks(device: Device, stripped_type: Optional[Type] = None): value.parent = sub_device else: if is_signal: - sub_device = device_cls(SimSignalBackend(signal_dtype, device_name)) + sub_device = device_cls(SimSignalBackend(signal_dtype)) else: sub_device = device_cls() diff --git a/src/ophyd_async/sim/pattern_generator.py b/src/ophyd_async/sim/pattern_generator.py index b6e5434eca..ebd7aed18f 100644 --- a/src/ophyd_async/sim/pattern_generator.py +++ b/src/ophyd_async/sim/pattern_generator.py @@ -54,7 +54,7 @@ def get_full_file_description( ): full_file_description: Dict[str, Descriptor] = {} for d in datasets: - source = f"sim://{d.name}" + source = f"soft://{d.name}" shape = outer_shape + tuple(d.shape) dtype = "number" if d.shape == [1] else "array" descriptor = Descriptor( @@ -158,7 +158,7 @@ def __init__( self.written_images_counter: int = 0 # it automatically initializes to 0 - self.signal_backend = SimSignalBackend(int, "sim://sim_images_counter") + self.signal_backend = SimSignalBackend(int) self.sim_signal = SignalR(self.signal_backend) blob = np.array( generate_gaussian_blob(width=detector_width, height=detector_height) diff --git a/tests/core/test_flyer.py b/tests/core/test_flyer.py index 53ff7313a7..5f6e5f438f 100644 --- a/tests/core/test_flyer.py +++ b/tests/core/test_flyer.py @@ -51,7 +51,7 @@ async def stop(self): class DummyWriter(DetectorWriter): def __init__(self, name: str, shape: Sequence[int]): - self.dummy_signal = SignalRW(backend=SimSignalBackend(int, source="test")) + self.dummy_signal = SignalRW(backend=SimSignalBackend(int)) self._shape = shape self._name = name self._file: Optional[ComposeStreamResourceBundle] = None @@ -61,7 +61,7 @@ def __init__(self, name: str, shape: Sequence[int]): async def open(self, multiplier: int = 1) -> Dict[str, Descriptor]: return { self._name: Descriptor( - source="sim://some-source", + source="soft://some-source", shape=self._shape, dtype="number", external="STREAM:", diff --git a/tests/core/test_signal.py b/tests/core/test_signal.py index 3b16601ddd..6796248a06 100644 --- a/tests/core/test_signal.py +++ b/tests/core/test_signal.py @@ -29,7 +29,7 @@ async def connect(self, sim=False, timeout=DEFAULT_TIMEOUT): def test_signals_equality_raises(): - sim_backend = SimSignalBackend(str, "test") + sim_backend = SimSignalBackend(str) s1 = MySignal(sim_backend) s2 = MySignal(sim_backend) @@ -48,7 +48,7 @@ def test_signals_equality_raises(): async def test_set_sim_put_proceeds(): - sim_signal = Signal(SimSignalBackend(str, "test")) + sim_signal = Signal(SimSignalBackend(str)) await sim_signal.connect(sim=True) assert sim_signal._backend.put_proceeds.is_set() is True @@ -66,7 +66,7 @@ async def time_taken_by(coro) -> float: async def test_wait_for_value_with_value(): - sim_signal = SignalRW(SimSignalBackend(str, "test")) + sim_signal = SignalRW(SimSignalBackend(str)) sim_signal.set_name("sim_signal") await sim_signal.connect(sim=True) set_sim_value(sim_signal, "blah") @@ -87,7 +87,7 @@ async def test_wait_for_value_with_value(): async def test_wait_for_value_with_funcion(): - sim_signal = SignalRW(SimSignalBackend(float, "test")) + sim_signal = SignalRW(SimSignalBackend(float)) sim_signal.set_name("sim_signal") await sim_signal.connect(sim=True) set_sim_value(sim_signal, 45.8) @@ -113,7 +113,7 @@ def less_than_42(v): async def test_set_and_wait_for_value(): - sim_signal = SignalRW(SimSignalBackend(int, "test")) + sim_signal = SignalRW(SimSignalBackend(int)) sim_signal.set_name("sim_signal") await sim_signal.connect(sim=True) set_sim_value(sim_signal, 0) @@ -129,15 +129,14 @@ async def test_set_and_wait_for_value(): [(soft_signal_r_and_backend, SignalR), (soft_signal_rw, SignalRW)], ) async def test_create_soft_signal(signal_method, signal_class): - TEST_PREFIX = "TEST-PREFIX" - SIGNAL_NAME = "SIGNAL" + SIGNAL_NAME = "TEST-PREFIX:SIGNAL" INITIAL_VALUE = "INITIAL" if signal_method == soft_signal_r_and_backend: - signal, backend = signal_method(str, SIGNAL_NAME, TEST_PREFIX, INITIAL_VALUE) + signal, backend = signal_method(str, SIGNAL_NAME, INITIAL_VALUE) elif signal_method == soft_signal_rw: - signal = signal_method(str, SIGNAL_NAME, TEST_PREFIX, INITIAL_VALUE) + signal = signal_method(str, SIGNAL_NAME, INITIAL_VALUE) backend = signal._backend - assert signal._backend.source == f"sim://{TEST_PREFIX}:{SIGNAL_NAME}" + assert signal.source == f"soft://{SIGNAL_NAME}" assert isinstance(signal, signal_class) assert isinstance(signal._backend, SimSignalBackend) await signal.connect() diff --git a/tests/core/test_sim.py b/tests/core/test_sim.py index baefc850ee..d02ed4ae1a 100644 --- a/tests/core/test_sim.py +++ b/tests/core/test_sim.py @@ -93,15 +93,16 @@ async def test_backend_get_put_monitor( put_value: T, descriptor: Callable[[Any], dict], ): - backend = SimSignalBackend(datatype, "") + backend = SimSignalBackend(datatype) await backend.connect() q = MonitorQueue(backend) try: # Check descriptor + source = "soft://test" assert ( - dict(source="sim://", **descriptor(initial_value)) - == await backend.get_descriptor() + dict(source=source, **descriptor(initial_value)) + == await backend.get_descriptor(source) ) # Check initial value await q.assert_updates( @@ -115,13 +116,13 @@ async def test_backend_get_put_monitor( async def test_sim_backend_if_disconnected(): - sim_backend = SimSignalBackend(npt.NDArray[np.float64], "SOME-IOC:PV") + sim_backend = SimSignalBackend(npt.NDArray[np.float64]) with pytest.raises(NotImplementedError): await sim_backend.get_value() async def test_sim_backend_with_numpy_typing(): - sim_backend = SimSignalBackend(npt.NDArray[np.float64], "SOME-IOC:PV") + sim_backend = SimSignalBackend(npt.NDArray[np.float64]) await sim_backend.connect() array = await sim_backend.get_value() @@ -133,8 +134,8 @@ class myClass: def __init__(self) -> None: pass - sim_signal = Signal(SimSignalBackend(myClass, "test")) + sim_signal = Signal(SimSignalBackend(myClass)) await sim_signal.connect(sim=True) with pytest.raises(AssertionError): - await sim_signal._backend.get_descriptor() + await sim_signal._backend.get_descriptor("") diff --git a/tests/core/test_utils.py b/tests/core/test_utils.py index 6a02bff77f..74ea8bc787 100644 --- a/tests/core/test_utils.py +++ b/tests/core/test_utils.py @@ -24,7 +24,7 @@ async def connect(self, timeout: float = DEFAULT_TIMEOUT): class WorkingDummyChildDevice(Device): def __init__(self, name: str = "working_dummy_child_device") -> None: - self.working_signal = SignalRW(backend=SimSignalBackend(int, "WORKING_SIGNAL")) + self.working_signal = SignalRW(backend=SimSignalBackend(int)) super().__init__(name=name) diff --git a/tests/epics/areadetector/test_single_trigger_det.py b/tests/epics/areadetector/test_single_trigger_det.py index 14d0ea06e8..684f72c3cf 100644 --- a/tests/epics/areadetector/test_single_trigger_det.py +++ b/tests/epics/areadetector/test_single_trigger_det.py @@ -44,9 +44,5 @@ async def test_single_trigger_det(single_trigger_det: SingleTriggerDet, RE: RunE assert names == ["start", "descriptor", "event", "stop"] _, descriptor, event, _ = docs assert descriptor["configuration"]["det"]["data"]["det-drv-acquire_time"] == 0.5 - assert ( - descriptor["data_keys"]["det-stats-unique_id"]["source"] - == "sim://PREFIX:STATSUniqueId_RBV" - ) assert event["data"]["det-drv-array_counter"] == 1 assert event["data"]["det-stats-unique_id"] == 3 diff --git a/tests/epics/areadetector/test_writers.py b/tests/epics/areadetector/test_writers.py index cad363af4b..dda8b74d44 100644 --- a/tests/epics/areadetector/test_writers.py +++ b/tests/epics/areadetector/test_writers.py @@ -40,7 +40,7 @@ async def test_correct_descriptor_doc_after_open(hdf_writer: HDFWriter): assert descriptor == { "test": { - "source": "sim://HDF:FullFileName_RBV", + "source": "soft://hdf-full_file_name", "shape": (10, 10), "dtype": "array", "external": "STREAM:", diff --git a/tests/epics/demo/test_demo.py b/tests/epics/demo/test_demo.py index 832143de99..ba4f941497 100644 --- a/tests/epics/demo/test_demo.py +++ b/tests/epics/demo/test_demo.py @@ -142,9 +142,6 @@ async def test_mover_stopped(sim_mover: demo.Mover): async def test_read_mover(sim_mover: demo.Mover): await sim_mover.stage() assert (await sim_mover.read())["sim_mover"]["value"] == 0.0 - assert (await sim_mover.describe())["sim_mover"][ - "source" - ] == "sim://BLxxI-MO-TABLE-01:X:Readback" assert (await sim_mover.read_configuration())["sim_mover-velocity"]["value"] == 1 assert (await sim_mover.describe_configuration())["sim_mover-units"]["shape"] == [] set_sim_value(sim_mover.readback, 0.5) @@ -158,9 +155,6 @@ async def test_read_mover(sim_mover: demo.Mover): async def test_set_velocity(sim_mover: demo.Mover) -> None: v = sim_mover.velocity - assert (await v.describe())["sim_mover-velocity"][ - "source" - ] == "sim://BLxxI-MO-TABLE-01:X:Velocity" q: asyncio.Queue[Dict[str, Reading]] = asyncio.Queue() v.subscribe(q.put_nowait) assert (await q.get())["sim_mover-velocity"]["value"] == 1.0 @@ -195,9 +189,6 @@ async def test_sensor_disconnected(caplog): async def test_read_sensor(sim_sensor: demo.Sensor): sim_sensor.stage() assert (await sim_sensor.read())["sim_sensor-value"]["value"] == 0 - assert (await sim_sensor.describe())["sim_sensor-value"][ - "source" - ] == "sim://SIM:SENSOR:Value" assert (await sim_sensor.read_configuration())["sim_sensor-mode"][ "value" ] == demo.EnergyMode.low @@ -261,17 +252,17 @@ async def test_dynamic_sensor_group_read_and_describe( "sim_sensor_group-sensors-1-value": { "dtype": "number", "shape": [], - "source": "sim://SIM:SENSOR:1:Value", + "source": "soft://sim_sensor_group-sensors-1-value", }, "sim_sensor_group-sensors-2-value": { "dtype": "number", "shape": [], - "source": "sim://SIM:SENSOR:2:Value", + "source": "soft://sim_sensor_group-sensors-2-value", }, "sim_sensor_group-sensors-3-value": { "dtype": "number", "shape": [], - "source": "sim://SIM:SENSOR:3:Value", + "source": "soft://sim_sensor_group-sensors-3-value", }, } assert reading == { diff --git a/tests/epics/demo/test_demo_ad_sim_detector.py b/tests/epics/demo/test_demo_ad_sim_detector.py index 241f974dec..32e0908de0 100644 --- a/tests/epics/demo/test_demo_ad_sim_detector.py +++ b/tests/epics/demo/test_demo_ad_sim_detector.py @@ -212,15 +212,14 @@ async def test_detector_writes_to_file( async def test_read_and_describe_detector(single_detector: StandardDetector): describe = await single_detector.describe_configuration() read = await single_detector.read_configuration() - assert describe == { "test-drv-acquire_time": { - "source": "sim://TEST:DRV:AcquireTime_RBV", + "source": "soft://test-drv-acquire_time", "dtype": "number", "shape": [], }, "test-drv-acquire": { - "source": "sim://TEST:DRV:Acquire_RBV", + "source": "soft://test-drv-acquire", "dtype": "boolean", "shape": [], }, diff --git a/tests/epics/motion/test_motor.py b/tests/epics/motion/test_motor.py index 32b270d2e7..c91b11aa0c 100644 --- a/tests/epics/motion/test_motor.py +++ b/tests/epics/motion/test_motor.py @@ -81,9 +81,6 @@ async def test_motor_moving_stopped(sim_motor: motor.Motor): async def test_read_motor(sim_motor: motor.Motor): sim_motor.stage() assert (await sim_motor.read())["sim_motor"]["value"] == 0.0 - assert (await sim_motor.describe())["sim_motor"][ - "source" - ] == "sim://BLxxI-MO-TABLE-01:X.RBV" assert (await sim_motor.read_configuration())["sim_motor-velocity"]["value"] == 1 assert (await sim_motor.describe_configuration())["sim_motor-motor_egu"][ "shape" @@ -99,9 +96,6 @@ async def test_read_motor(sim_motor: motor.Motor): async def test_set_velocity(sim_motor: motor.Motor) -> None: v = sim_motor.velocity - assert (await v.describe())["sim_motor-velocity"][ - "source" - ] == "sim://BLxxI-MO-TABLE-01:X.VELO" q: asyncio.Queue[Dict[str, Reading]] = asyncio.Queue() v.subscribe(q.put_nowait) assert (await q.get())["sim_motor-velocity"]["value"] == 1.0 diff --git a/tests/epics/test_signals.py b/tests/epics/test_signals.py index d805ca25a5..18c9f4fba5 100644 --- a/tests/epics/test_signals.py +++ b/tests/epics/test_signals.py @@ -125,8 +125,10 @@ async def assert_monitor_then_put( q = MonitorQueue(backend) try: # Check descriptor - source = f"{ioc.protocol}://{PV_PREFIX}:{ioc.protocol}:{suffix}" - assert dict(source=source, **descriptor) == await backend.get_descriptor() + pv_name = f"{ioc.protocol}://{PV_PREFIX}:{ioc.protocol}:{suffix}" + assert dict(source=pv_name, **descriptor) == await backend.get_descriptor( + pv_name + ) # Check initial value await q.assert_updates(pytest.approx(initial_value)) # Put to new value and check that @@ -400,7 +402,9 @@ async def test_pva_table(ioc: IOC) -> None: q = MonitorQueue(backend) try: # Check descriptor - dict(source=backend.source, **descriptor) == await backend.get_descriptor() + dict(source="test-source", **descriptor) == await backend.get_descriptor( + "test-source" + ) # Check initial value await q.assert_updates(approx_table(i)) # Put to new value and check that @@ -435,7 +439,7 @@ async def test_pvi_structure(ioc: IOC) -> None: try: # Check descriptor with pytest.raises(NotImplementedError): - await backend.get_descriptor() + await backend.get_descriptor("") # Check initial value await q.assert_updates(expected) await backend.get_value() @@ -462,10 +466,10 @@ async def test_pva_ntdarray(ioc: IOC): for i, p in [(initial, put), (put, initial)]: with closing(MonitorQueue(backend)) as q: assert { - "source": backend.source, + "source": "test-source", "dtype": "array", "shape": [2, 3], - } == await backend.get_descriptor() + } == await backend.get_descriptor("test-source") # Check initial value await q.assert_updates(pytest.approx(i)) await raw_data_backend.put(p.flatten()) @@ -487,8 +491,7 @@ async def test_non_existent_errors(ioc: IOC): backend = await ioc.make_backend(str, "non-existent", connect=False) # Can't use asyncio.wait_for on python3.8 because of # https://github.com/python/cpython/issues/84787 - - with pytest.raises(NotConnected, match=backend.source): + with pytest.raises(NotConnected): await backend.connect(timeout=0.1) diff --git a/tests/panda/test_writer.py b/tests/panda/test_writer.py index c80893823c..8a9064edc4 100644 --- a/tests/panda/test_writer.py +++ b/tests/panda/test_writer.py @@ -30,10 +30,10 @@ async def sim_panda() -> PandA: sim_panda.block1 = Device("BLOCK1") # type: ignore[attr-defined] sim_panda.block2 = Device("BLOCK2") # type: ignore[attr-defined] sim_panda.block1.test_capture = SignalRW( # type: ignore[attr-defined] - backend=SimSignalBackend(str, source="BLOCK1_capture") + backend=SimSignalBackend(str) ) sim_panda.block2.test_capture = SignalRW( # type: ignore[attr-defined] - backend=SimSignalBackend(str, source="BLOCK2_capture") + backend=SimSignalBackend(str) ) await asyncio.gather( @@ -70,10 +70,10 @@ async def test_get_capture_signals_gets_all_signals(sim_panda): async with DeviceCollector(sim=True): sim_panda.test_seq = Device("seq") sim_panda.test_seq.seq1_capture = SignalR( - backend=SimSignalBackend(str, source="seq1_capture") + backend=SimSignalBackend(str) ) sim_panda.test_seq.seq2_capture = SignalR( - backend=SimSignalBackend(str, source="seq2_capture") + backend=SimSignalBackend(str) ) await asyncio.gather( sim_panda.test_seq.connect(), @@ -181,7 +181,7 @@ async def test_numeric_blocks_correctly_formated(sim_writer: PandaHDFWriter): async def get_numeric_signal(_): return { "device.block.1": CaptureSignalWrapper( - SignalR(backend=SimSignalBackend(str, source="test_signal")), + SignalR(backend=SimSignalBackend(str)), Capture.Value, ) } diff --git a/tests/sim/test_sim_writer.py b/tests/sim/test_sim_writer.py index 59ecd5a494..d26fd3beaa 100644 --- a/tests/sim/test_sim_writer.py +++ b/tests/sim/test_sim_writer.py @@ -23,13 +23,13 @@ async def test_correct_descriptor_doc_after_open(writer: SimPatternDetectorWrite assert descriptor == { "_entry_data_data": { - "source": "sim:///entry/data/data", + "source": "soft:///entry/data/data", "shape": (1, 240, 320), "dtype": "array", "external": "STREAM:", }, "_entry_sum": { - "source": "sim:///entry/sum", + "source": "soft:///entry/sum", "shape": (1,), "dtype": "array", "external": "STREAM:", diff --git a/tests/test_flyer_with_panda.py b/tests/test_flyer_with_panda.py index f680ad0395..d0cfab8221 100644 --- a/tests/test_flyer_with_panda.py +++ b/tests/test_flyer_with_panda.py @@ -28,7 +28,7 @@ class DummyWriter(DetectorWriter): def __init__(self, name: str, shape: Sequence[int]): - self.dummy_signal = SignalRW(backend=SimSignalBackend(int, source="test")) + self.dummy_signal = SignalRW(backend=SimSignalBackend(int)) self._shape = shape self._name = name self._file: Optional[ComposeStreamResourceBundle] = None @@ -38,7 +38,7 @@ def __init__(self, name: str, shape: Sequence[int]): async def open(self, multiplier: int = 1) -> Dict[str, Descriptor]: return { self._name: Descriptor( - source="sim://some-source", + source="soft://some-source", shape=self._shape, dtype="number", external="STREAM:", From 0de1c562fe3fb3b095d298ceeb52e31bb3a740fb Mon Sep 17 00:00:00 2001 From: James Souter Date: Fri, 19 Apr 2024 08:35:18 +0000 Subject: [PATCH 6/6] Support numpy floating and integer dtypes in SimConverter test numpy dtype with SimSignalBackend --- src/ophyd_async/core/signal.py | 22 ++++++++++------------ src/ophyd_async/core/sim_signal_backend.py | 12 +++++++++--- tests/core/test_signal.py | 14 ++++++++++++-- tests/core/test_sim.py | 7 +++---- tests/panda/test_writer.py | 8 ++------ 5 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/ophyd_async/core/signal.py b/src/ophyd_async/core/signal.py index a9c76af3f4..f206803475 100644 --- a/src/ophyd_async/core/signal.py +++ b/src/ophyd_async/core/signal.py @@ -60,9 +60,7 @@ def set_name(self, name: str = ""): async def connect(self, sim=False, timeout=DEFAULT_TIMEOUT): if sim: - self._backend = SimSignalBackend( - datatype=self._init_backend.datatype - ) + self._backend = SimSignalBackend(datatype=self._init_backend.datatype) _sim_backends[self] = self._backend else: self._backend = self._init_backend @@ -254,22 +252,21 @@ def set_sim_callback(signal: Signal[T], callback: ReadingValueCallback[T]) -> No def soft_signal_rw( - datatype: Optional[Type[T]], - name: str, + datatype: Optional[Type[T]] = None, initial_value: Optional[T] = None, + name: Optional[str] = None, ) -> SignalRW[T]: """Creates a read-writable Signal with a SimSignalBackend""" - signal = SignalRW( - SimSignalBackend(datatype, initial_value) - ) - signal.set_name(name) + signal = SignalRW(SimSignalBackend(datatype, initial_value)) + if name is not None: + signal.set_name(name) return signal def soft_signal_r_and_backend( - datatype: Optional[Type[T]], - name: str, + datatype: Optional[Type[T]] = None, initial_value: Optional[T] = None, + name: Optional[str] = None, ) -> Tuple[SignalR[T], SimSignalBackend]: """Returns a tuple of a read-only Signal and its SimSignalBackend through which the signal can be internally modified within the device. Use @@ -277,7 +274,8 @@ def soft_signal_r_and_backend( """ backend = SimSignalBackend(datatype, initial_value) signal = SignalR(backend) - signal.set_name(name) + if name is not None: + signal.set_name(name) return (signal, backend) diff --git a/src/ophyd_async/core/sim_signal_backend.py b/src/ophyd_async/core/sim_signal_backend.py index 267caaa612..1c3e10ce83 100644 --- a/src/ophyd_async/core/sim_signal_backend.py +++ b/src/ophyd_async/core/sim_signal_backend.py @@ -8,6 +8,7 @@ from enum import Enum from typing import Any, Dict, Generic, Optional, Type, Union, cast, get_origin +import numpy as np from bluesky.protocols import Descriptor, Dtype, Reading from .signal_backend import SignalBackend @@ -36,11 +37,16 @@ def reading(self, value: T, timestamp: float, severity: int) -> Reading: ) def descriptor(self, source: str, value) -> Descriptor: + dtype = type(value) + if np.issubdtype(dtype, np.integer): + dtype = int + elif np.issubdtype(dtype, np.floating): + dtype = float assert ( - type(value) in primitive_dtypes + dtype in primitive_dtypes ), f"invalid converter for value of type {type(value)}" - dtype = primitive_dtypes[type(value)] - return {"source": source, "dtype": dtype, "shape": []} + dtype_name = primitive_dtypes[dtype] + return {"source": source, "dtype": dtype_name, "shape": []} def make_initial_value(self, datatype: Optional[Type[T]]) -> T: if datatype is None: diff --git a/tests/core/test_signal.py b/tests/core/test_signal.py index 6796248a06..f2aa4252be 100644 --- a/tests/core/test_signal.py +++ b/tests/core/test_signal.py @@ -2,6 +2,7 @@ import re import time +import numpy import pytest from ophyd_async.core import ( @@ -132,9 +133,9 @@ async def test_create_soft_signal(signal_method, signal_class): SIGNAL_NAME = "TEST-PREFIX:SIGNAL" INITIAL_VALUE = "INITIAL" if signal_method == soft_signal_r_and_backend: - signal, backend = signal_method(str, SIGNAL_NAME, INITIAL_VALUE) + signal, backend = signal_method(str, INITIAL_VALUE, SIGNAL_NAME) elif signal_method == soft_signal_rw: - signal = signal_method(str, SIGNAL_NAME, INITIAL_VALUE) + signal = signal_method(str, INITIAL_VALUE, SIGNAL_NAME) backend = signal._backend assert signal.source == f"soft://{SIGNAL_NAME}" assert isinstance(signal, signal_class) @@ -143,3 +144,12 @@ async def test_create_soft_signal(signal_method, signal_class): assert (await signal.get_value()) == INITIAL_VALUE # connecting with sim=False uses existing SimSignalBackend assert signal._backend is backend + + +async def test_soft_signal_numpy(): + float_signal = soft_signal_rw(numpy.float64, numpy.float64(1), "float_signal") + int_signal = soft_signal_rw(numpy.int32, numpy.int32(1), "int_signal") + await float_signal.connect() + await int_signal.connect() + assert (await float_signal.describe())["float_signal"]["dtype"] == "number" + assert (await int_signal.describe())["int_signal"]["dtype"] == "integer" diff --git a/tests/core/test_sim.py b/tests/core/test_sim.py index d02ed4ae1a..a2897ed984 100644 --- a/tests/core/test_sim.py +++ b/tests/core/test_sim.py @@ -100,10 +100,9 @@ async def test_backend_get_put_monitor( try: # Check descriptor source = "soft://test" - assert ( - dict(source=source, **descriptor(initial_value)) - == await backend.get_descriptor(source) - ) + assert dict( + source=source, **descriptor(initial_value) + ) == await backend.get_descriptor(source) # Check initial value await q.assert_updates( pytest.approx(initial_value) if initial_value != "" else initial_value diff --git a/tests/panda/test_writer.py b/tests/panda/test_writer.py index 8a9064edc4..ffb50dff4e 100644 --- a/tests/panda/test_writer.py +++ b/tests/panda/test_writer.py @@ -69,12 +69,8 @@ async def sim_writer(tmp_path, sim_panda) -> PandaHDFWriter: async def test_get_capture_signals_gets_all_signals(sim_panda): async with DeviceCollector(sim=True): sim_panda.test_seq = Device("seq") - sim_panda.test_seq.seq1_capture = SignalR( - backend=SimSignalBackend(str) - ) - sim_panda.test_seq.seq2_capture = SignalR( - backend=SimSignalBackend(str) - ) + sim_panda.test_seq.seq1_capture = SignalR(backend=SimSignalBackend(str)) + sim_panda.test_seq.seq2_capture = SignalR(backend=SimSignalBackend(str)) await asyncio.gather( sim_panda.test_seq.connect(), sim_panda.test_seq.seq1_capture.connect(),