Skip to content

Commit

Permalink
Make backend source a method.
Browse files Browse the repository at this point in the history
signals can pass a value to the backend, signal sources are now properties
  • Loading branch information
James Souter committed Apr 18, 2024
1 parent 3122e87 commit bc4e98b
Show file tree
Hide file tree
Showing 17 changed files with 65 additions and 78 deletions.
17 changes: 9 additions & 8 deletions src/ophyd_async/core/signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)


Expand Down
7 changes: 5 additions & 2 deletions src/ophyd_async/core/signal_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
Expand Down
11 changes: 5 additions & 6 deletions src/ophyd_async/core/sim_signal_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,19 +116,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:
Expand Down Expand Up @@ -162,8 +161,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)
Expand Down
8 changes: 5 additions & 3 deletions src/ophyd_async/epics/_backend/_aioca.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 6 additions & 3 deletions src/ophyd_async/epics/_backend/_p4p.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
"""
Expand Down
6 changes: 3 additions & 3 deletions src/ophyd_async/epics/pvi/pvi.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ def _sim_common_blocks(device: Device, stripped_type: Optional[Type] = None):
if is_device_vector:
if is_signal:
signal_type = args[0] if (args := get_args(sub_device_t)) else None
sub_device_1 = sub_device_t(SimSignalBackend(signal_type, sub_name))
sub_device_2 = sub_device_t(SimSignalBackend(signal_type, sub_name))
sub_device_1 = sub_device_t(SimSignalBackend(signal_type))
sub_device_2 = sub_device_t(SimSignalBackend(signal_type))
sub_device = DeviceVector(
{
1: sub_device_1,
Expand All @@ -183,7 +183,7 @@ def _sim_common_blocks(device: Device, stripped_type: Optional[Type] = None):

elif is_signal:
signal_type = args[0] if (args := get_args(sub_device_t)) else None
sub_device = sub_device_t(SimSignalBackend(signal_type, sub_name))
sub_device = sub_device_t(SimSignalBackend(signal_type))
else:
sub_device = sub_device_t()

Expand Down
4 changes: 2 additions & 2 deletions tests/core/test_flyer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:",
Expand Down
19 changes: 9 additions & 10 deletions tests/core/test_signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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")
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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()
Expand Down
15 changes: 8 additions & 7 deletions tests/core/test_sim.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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()
Expand All @@ -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("")
2 changes: 1 addition & 1 deletion tests/core/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down
4 changes: 0 additions & 4 deletions tests/epics/areadetector/test_single_trigger_det.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion tests/epics/areadetector/test_writers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:",
Expand Down
9 changes: 0 additions & 9 deletions tests/epics/demo/test_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,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)
Expand All @@ -147,9 +144,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
Expand Down Expand Up @@ -184,9 +178,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
Expand Down
5 changes: 2 additions & 3 deletions tests/epics/demo/test_demo_ad_sim_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": [],
},
Expand Down
Loading

0 comments on commit bc4e98b

Please sign in to comment.