ophyd-async 0.9.0a2__py3-none-any.whl → 0.10.0a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ophyd_async/__init__.py +5 -8
- ophyd_async/_docs_parser.py +12 -0
- ophyd_async/_version.py +9 -4
- ophyd_async/core/__init__.py +97 -62
- ophyd_async/core/_derived_signal.py +271 -0
- ophyd_async/core/_derived_signal_backend.py +300 -0
- ophyd_async/core/_detector.py +106 -125
- ophyd_async/core/_device.py +69 -63
- ophyd_async/core/_device_filler.py +65 -1
- ophyd_async/core/_flyer.py +14 -5
- ophyd_async/core/_hdf_dataset.py +29 -22
- ophyd_async/core/_log.py +14 -23
- ophyd_async/core/_mock_signal_backend.py +11 -3
- ophyd_async/core/_protocol.py +65 -45
- ophyd_async/core/_providers.py +28 -9
- ophyd_async/core/_readable.py +44 -35
- ophyd_async/core/_settings.py +36 -27
- ophyd_async/core/_signal.py +262 -170
- ophyd_async/core/_signal_backend.py +56 -13
- ophyd_async/core/_soft_signal_backend.py +16 -11
- ophyd_async/core/_status.py +72 -24
- ophyd_async/core/_table.py +41 -11
- ophyd_async/core/_utils.py +96 -49
- ophyd_async/core/_yaml_settings.py +2 -0
- ophyd_async/epics/__init__.py +1 -0
- ophyd_async/epics/adandor/_andor.py +2 -2
- ophyd_async/epics/adandor/_andor_controller.py +4 -2
- ophyd_async/epics/adandor/_andor_io.py +2 -4
- ophyd_async/epics/adaravis/__init__.py +5 -0
- ophyd_async/epics/adaravis/_aravis.py +4 -8
- ophyd_async/epics/adaravis/_aravis_controller.py +20 -43
- ophyd_async/epics/adaravis/_aravis_io.py +13 -28
- ophyd_async/epics/adcore/__init__.py +23 -8
- ophyd_async/epics/adcore/_core_detector.py +42 -2
- ophyd_async/epics/adcore/_core_io.py +124 -99
- ophyd_async/epics/adcore/_core_logic.py +106 -27
- ophyd_async/epics/adcore/_core_writer.py +12 -8
- ophyd_async/epics/adcore/_hdf_writer.py +21 -38
- ophyd_async/epics/adcore/_single_trigger.py +2 -2
- ophyd_async/epics/adcore/_utils.py +2 -2
- ophyd_async/epics/adkinetix/__init__.py +2 -1
- ophyd_async/epics/adkinetix/_kinetix.py +3 -3
- ophyd_async/epics/adkinetix/_kinetix_controller.py +4 -2
- ophyd_async/epics/adkinetix/_kinetix_io.py +12 -13
- ophyd_async/epics/adpilatus/__init__.py +5 -0
- ophyd_async/epics/adpilatus/_pilatus.py +1 -1
- ophyd_async/epics/adpilatus/_pilatus_controller.py +5 -24
- ophyd_async/epics/adpilatus/_pilatus_io.py +11 -9
- ophyd_async/epics/adsimdetector/__init__.py +8 -1
- ophyd_async/epics/adsimdetector/_sim.py +4 -14
- ophyd_async/epics/adsimdetector/_sim_controller.py +17 -0
- ophyd_async/epics/adsimdetector/_sim_io.py +10 -0
- ophyd_async/epics/advimba/__init__.py +10 -1
- ophyd_async/epics/advimba/_vimba.py +3 -2
- ophyd_async/epics/advimba/_vimba_controller.py +4 -2
- ophyd_async/epics/advimba/_vimba_io.py +23 -28
- ophyd_async/epics/core/_aioca.py +35 -16
- ophyd_async/epics/core/_epics_connector.py +4 -0
- ophyd_async/epics/core/_epics_device.py +2 -0
- ophyd_async/epics/core/_p4p.py +10 -2
- ophyd_async/epics/core/_pvi_connector.py +65 -8
- ophyd_async/epics/core/_signal.py +51 -51
- ophyd_async/epics/core/_util.py +4 -4
- ophyd_async/epics/demo/__init__.py +16 -0
- ophyd_async/epics/demo/__main__.py +31 -0
- ophyd_async/epics/demo/_ioc.py +32 -0
- ophyd_async/epics/demo/_motor.py +82 -0
- ophyd_async/epics/demo/_point_detector.py +42 -0
- ophyd_async/epics/demo/_point_detector_channel.py +22 -0
- ophyd_async/epics/demo/_stage.py +15 -0
- ophyd_async/epics/{sim/mover.db → demo/motor.db} +2 -1
- ophyd_async/epics/demo/point_detector.db +59 -0
- ophyd_async/epics/demo/point_detector_channel.db +21 -0
- ophyd_async/epics/eiger/_eiger.py +1 -3
- ophyd_async/epics/eiger/_eiger_controller.py +11 -4
- ophyd_async/epics/eiger/_eiger_io.py +2 -0
- ophyd_async/epics/eiger/_odin_io.py +1 -2
- ophyd_async/epics/motor.py +65 -28
- ophyd_async/epics/signal.py +4 -1
- ophyd_async/epics/testing/_example_ioc.py +21 -9
- ophyd_async/epics/testing/_utils.py +3 -0
- ophyd_async/epics/testing/test_records.db +8 -0
- ophyd_async/epics/testing/test_records_pva.db +17 -16
- ophyd_async/fastcs/__init__.py +1 -0
- ophyd_async/fastcs/core.py +6 -0
- ophyd_async/fastcs/odin/__init__.py +1 -0
- ophyd_async/fastcs/panda/__init__.py +8 -6
- ophyd_async/fastcs/panda/_block.py +29 -9
- ophyd_async/fastcs/panda/_control.py +5 -0
- ophyd_async/fastcs/panda/_hdf_panda.py +2 -0
- ophyd_async/fastcs/panda/_table.py +9 -6
- ophyd_async/fastcs/panda/_trigger.py +23 -9
- ophyd_async/fastcs/panda/_writer.py +27 -30
- ophyd_async/plan_stubs/__init__.py +2 -0
- ophyd_async/plan_stubs/_ensure_connected.py +1 -0
- ophyd_async/plan_stubs/_fly.py +2 -4
- ophyd_async/plan_stubs/_nd_attributes.py +2 -0
- ophyd_async/plan_stubs/_panda.py +1 -0
- ophyd_async/plan_stubs/_settings.py +43 -16
- ophyd_async/plan_stubs/_utils.py +3 -0
- ophyd_async/plan_stubs/_wait_for_awaitable.py +1 -1
- ophyd_async/sim/__init__.py +24 -14
- ophyd_async/sim/__main__.py +43 -0
- ophyd_async/sim/_blob_detector.py +33 -0
- ophyd_async/sim/_blob_detector_controller.py +48 -0
- ophyd_async/sim/_blob_detector_writer.py +105 -0
- ophyd_async/sim/_mirror_horizontal.py +46 -0
- ophyd_async/sim/_mirror_vertical.py +74 -0
- ophyd_async/sim/_motor.py +233 -0
- ophyd_async/sim/_pattern_generator.py +124 -0
- ophyd_async/sim/_point_detector.py +86 -0
- ophyd_async/sim/_stage.py +19 -0
- ophyd_async/tango/__init__.py +1 -0
- ophyd_async/tango/core/__init__.py +6 -1
- ophyd_async/tango/core/_base_device.py +41 -33
- ophyd_async/tango/core/_converters.py +81 -0
- ophyd_async/tango/core/_signal.py +18 -32
- ophyd_async/tango/core/_tango_readable.py +2 -19
- ophyd_async/tango/core/_tango_transport.py +136 -60
- ophyd_async/tango/core/_utils.py +47 -0
- ophyd_async/tango/{sim → demo}/_counter.py +2 -0
- ophyd_async/tango/{sim → demo}/_detector.py +2 -0
- ophyd_async/tango/{sim → demo}/_mover.py +5 -4
- ophyd_async/tango/{sim → demo}/_tango/_servers.py +4 -0
- ophyd_async/tango/testing/__init__.py +6 -0
- ophyd_async/tango/testing/_one_of_everything.py +200 -0
- ophyd_async/testing/__init__.py +29 -7
- ophyd_async/testing/_assert.py +145 -83
- ophyd_async/testing/_mock_signal_utils.py +56 -70
- ophyd_async/testing/_one_of_everything.py +41 -21
- ophyd_async/testing/_single_derived.py +89 -0
- ophyd_async/testing/_utils.py +3 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.dist-info}/METADATA +25 -26
- ophyd_async-0.10.0a2.dist-info/RECORD +149 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.dist-info}/WHEEL +1 -1
- ophyd_async/epics/sim/__init__.py +0 -54
- ophyd_async/epics/sim/_ioc.py +0 -29
- ophyd_async/epics/sim/_mover.py +0 -101
- ophyd_async/epics/sim/_sensor.py +0 -37
- ophyd_async/epics/sim/sensor.db +0 -19
- ophyd_async/sim/_pattern_detector/__init__.py +0 -13
- ophyd_async/sim/_pattern_detector/_pattern_detector.py +0 -42
- ophyd_async/sim/_pattern_detector/_pattern_detector_controller.py +0 -69
- ophyd_async/sim/_pattern_detector/_pattern_detector_writer.py +0 -41
- ophyd_async/sim/_pattern_detector/_pattern_generator.py +0 -214
- ophyd_async/sim/_sim_motor.py +0 -107
- ophyd_async-0.9.0a2.dist-info/RECORD +0 -129
- /ophyd_async/tango/{sim → demo}/__init__.py +0 -0
- /ophyd_async/tango/{sim → demo}/_tango/__init__.py +0 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.dist-info/licenses}/LICENSE +0 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
5
|
+
from functools import cached_property
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar, is_typeddict
|
|
7
|
+
|
|
8
|
+
from bluesky.protocols import Location, Reading, Subscribable
|
|
9
|
+
from event_model import DataKey
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
from ._protocol import AsyncLocatable, AsyncReadable
|
|
13
|
+
from ._signal_backend import SignalBackend, SignalDatatypeT, make_datakey, make_metadata
|
|
14
|
+
from ._utils import Callback, T, gather_dict, merge_gathered_dicts
|
|
15
|
+
|
|
16
|
+
RawT = TypeVar("RawT")
|
|
17
|
+
DerivedT = TypeVar("DerivedT")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Transform(BaseModel, Generic[RawT, DerivedT]):
|
|
21
|
+
"""Baseclass for bidirectional transforms for Derived Signals.
|
|
22
|
+
|
|
23
|
+
Subclass and add:
|
|
24
|
+
- type hinted parameters that should be fetched from Signals
|
|
25
|
+
- a raw_to_derived method that takes the elements of RawT and returns a DerivedT
|
|
26
|
+
- a derived_to_raw method that takes the elements of DerivedT and returns a RawT
|
|
27
|
+
|
|
28
|
+
:example:
|
|
29
|
+
```python
|
|
30
|
+
class MyRaw(TypedDict):
|
|
31
|
+
raw1: float
|
|
32
|
+
raw2: float
|
|
33
|
+
|
|
34
|
+
class MyDerived(TypedDict):
|
|
35
|
+
derived1: float
|
|
36
|
+
derived2: float
|
|
37
|
+
|
|
38
|
+
class MyTransform(Transform):
|
|
39
|
+
param1: float
|
|
40
|
+
|
|
41
|
+
def raw_to_derived(self, *, raw1: float, raw2: float) -> MyDerived:
|
|
42
|
+
derived1, derived2 = some_maths(self.param1, raw1, raw2)
|
|
43
|
+
return MyDerived(derived1=derived1, derived2=derived2)
|
|
44
|
+
|
|
45
|
+
def derived_to_raw(self, *, derived1: float, derived2: float) -> MyRaw:
|
|
46
|
+
raw1, raw2 = some_inverse_maths(self.param1, derived1, derived2)
|
|
47
|
+
return MyRaw(raw1=raw1, raw2=raw2)
|
|
48
|
+
```
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
if TYPE_CHECKING:
|
|
52
|
+
# Guard with if type checking so they don't appear in pydantic argument list
|
|
53
|
+
# Ideally they would be:
|
|
54
|
+
# def raw_to_derived(self, **kwargs: Unpack[RawT]) -> DerivedT: ...
|
|
55
|
+
# but TypedDicts are not valid as generics
|
|
56
|
+
# https://github.com/microsoft/pyright/discussions/7317
|
|
57
|
+
raw_to_derived: Callable[..., DerivedT]
|
|
58
|
+
derived_to_raw: Callable[..., RawT]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
TransformT = TypeVar("TransformT", bound=Transform)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def filter_by_type(raw_devices: Mapping[str, Any], type_: type[T]) -> dict[str, T]:
|
|
65
|
+
filtered_devices: dict[str, T] = {}
|
|
66
|
+
for name, device in raw_devices.items():
|
|
67
|
+
if not isinstance(device, type_):
|
|
68
|
+
msg = f"{device} is not an instance of {type_}"
|
|
69
|
+
raise TypeError(msg)
|
|
70
|
+
filtered_devices[name] = device
|
|
71
|
+
return filtered_devices
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class SignalTransformer(Generic[TransformT]):
|
|
75
|
+
def __init__(
|
|
76
|
+
self,
|
|
77
|
+
transform_cls: type[TransformT],
|
|
78
|
+
set_derived: Callable[..., Awaitable[None]] | None,
|
|
79
|
+
set_derived_datatype: type | None,
|
|
80
|
+
**raw_and_transform_devices,
|
|
81
|
+
):
|
|
82
|
+
self._transform_cls = transform_cls
|
|
83
|
+
self._set_derived = set_derived
|
|
84
|
+
self._need_dict = is_typeddict(set_derived_datatype)
|
|
85
|
+
self._transform_devices = {
|
|
86
|
+
k: raw_and_transform_devices.pop(k) for k in transform_cls.model_fields
|
|
87
|
+
}
|
|
88
|
+
self._raw_devices = raw_and_transform_devices
|
|
89
|
+
self._derived_callbacks: dict[str, Callback[Reading]] = {}
|
|
90
|
+
self._cached_readings: dict[str, Reading] | None = None
|
|
91
|
+
|
|
92
|
+
@cached_property
|
|
93
|
+
def raw_locatables(self) -> dict[str, AsyncLocatable]:
|
|
94
|
+
return filter_by_type(self._raw_devices, AsyncLocatable)
|
|
95
|
+
|
|
96
|
+
@cached_property
|
|
97
|
+
def transform_readables(self) -> dict[str, AsyncReadable]:
|
|
98
|
+
return filter_by_type(self._transform_devices, AsyncReadable)
|
|
99
|
+
|
|
100
|
+
@cached_property
|
|
101
|
+
def raw_and_transform_readables(self) -> dict[str, AsyncReadable]:
|
|
102
|
+
return filter_by_type(
|
|
103
|
+
self._raw_devices | self._transform_devices, AsyncReadable
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
@cached_property
|
|
107
|
+
def raw_and_transform_subscribables(self) -> dict[str, Subscribable]:
|
|
108
|
+
return filter_by_type(self._raw_devices | self._transform_devices, Subscribable)
|
|
109
|
+
|
|
110
|
+
def _complete_cached_reading(self) -> dict[str, Reading] | None:
|
|
111
|
+
if self._cached_readings and len(self._cached_readings) == len(
|
|
112
|
+
self.raw_and_transform_subscribables
|
|
113
|
+
):
|
|
114
|
+
return self._cached_readings
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
def _make_transform_from_readings(
|
|
118
|
+
self, transform_readings: dict[str, Reading]
|
|
119
|
+
) -> TransformT:
|
|
120
|
+
# Make the transform using the values from the readings for those args
|
|
121
|
+
transform_args = {
|
|
122
|
+
k: transform_readings[sig.name]["value"]
|
|
123
|
+
for k, sig in self.transform_readables.items()
|
|
124
|
+
}
|
|
125
|
+
return self._transform_cls(**transform_args)
|
|
126
|
+
|
|
127
|
+
def _make_derived_readings(
|
|
128
|
+
self, raw_and_transform_readings: dict[str, Reading]
|
|
129
|
+
) -> dict[str, Reading]:
|
|
130
|
+
# Calculate the latest timestamp and max severity from them
|
|
131
|
+
timestamp = max(
|
|
132
|
+
raw_and_transform_readings[device.name]["timestamp"]
|
|
133
|
+
for device in self.raw_and_transform_subscribables.values()
|
|
134
|
+
)
|
|
135
|
+
alarm_severity = max(
|
|
136
|
+
raw_and_transform_readings[device.name].get("alarm_severity", 0)
|
|
137
|
+
for device in self.raw_and_transform_subscribables.values()
|
|
138
|
+
)
|
|
139
|
+
# Make the transform using the values from the readings for those args
|
|
140
|
+
transform = self._make_transform_from_readings(raw_and_transform_readings)
|
|
141
|
+
# Create the raw values from the rest then calculate the derived readings
|
|
142
|
+
# using the transform
|
|
143
|
+
raw_values = {
|
|
144
|
+
k: raw_and_transform_readings[sig.name]["value"]
|
|
145
|
+
for k, sig in self._raw_devices.items()
|
|
146
|
+
}
|
|
147
|
+
derived_readings = {
|
|
148
|
+
name: Reading(
|
|
149
|
+
value=derived, timestamp=timestamp, alarm_severity=alarm_severity
|
|
150
|
+
)
|
|
151
|
+
for name, derived in transform.raw_to_derived(**raw_values).items()
|
|
152
|
+
}
|
|
153
|
+
return derived_readings
|
|
154
|
+
|
|
155
|
+
async def get_transform(self) -> TransformT:
|
|
156
|
+
if raw_and_transform_readings := self._complete_cached_reading():
|
|
157
|
+
transform_readings = raw_and_transform_readings
|
|
158
|
+
else:
|
|
159
|
+
transform_readings = await merge_gathered_dicts(
|
|
160
|
+
device.read() for device in self.transform_readables.values()
|
|
161
|
+
)
|
|
162
|
+
return self._make_transform_from_readings(transform_readings)
|
|
163
|
+
|
|
164
|
+
async def get_derived_readings(self) -> dict[str, Reading]:
|
|
165
|
+
if not (raw_and_transform_readings := self._complete_cached_reading()):
|
|
166
|
+
raw_and_transform_readings = await merge_gathered_dicts(
|
|
167
|
+
device.read() for device in self.raw_and_transform_readables.values()
|
|
168
|
+
)
|
|
169
|
+
return self._make_derived_readings(raw_and_transform_readings)
|
|
170
|
+
|
|
171
|
+
async def get_derived_values(self) -> dict[str, Any]:
|
|
172
|
+
derived_readings = await self.get_derived_readings()
|
|
173
|
+
return {k: v["value"] for k, v in derived_readings.items()}
|
|
174
|
+
|
|
175
|
+
def _update_cached_reading(self, value: dict[str, Reading]):
|
|
176
|
+
if self._cached_readings is None:
|
|
177
|
+
msg = "Cannot update cached reading as it has not been initialised"
|
|
178
|
+
raise RuntimeError(msg)
|
|
179
|
+
self._cached_readings.update(value)
|
|
180
|
+
if self._complete_cached_reading():
|
|
181
|
+
# We've got a complete set of values, callback on them
|
|
182
|
+
derived_readings = self._make_derived_readings(self._cached_readings)
|
|
183
|
+
for name, callback in self._derived_callbacks.items():
|
|
184
|
+
callback(derived_readings[name])
|
|
185
|
+
|
|
186
|
+
def set_callback(self, name: str, callback: Callback[Reading] | None) -> None:
|
|
187
|
+
if callback is None:
|
|
188
|
+
self._derived_callbacks.pop(name, None)
|
|
189
|
+
if not self._derived_callbacks:
|
|
190
|
+
# Remove the callbacks to all the raw devices
|
|
191
|
+
for raw in self.raw_and_transform_subscribables.values():
|
|
192
|
+
raw.clear_sub(self._update_cached_reading)
|
|
193
|
+
# and clear the cached readings that will now be stale
|
|
194
|
+
self._cached_readings = None
|
|
195
|
+
else:
|
|
196
|
+
if name in self._derived_callbacks:
|
|
197
|
+
msg = f"Callback already set for {name}"
|
|
198
|
+
raise RuntimeError(msg)
|
|
199
|
+
self._derived_callbacks[name] = callback
|
|
200
|
+
if self._cached_readings is None:
|
|
201
|
+
# Add the callbacks to all the raw devices, this will run the first
|
|
202
|
+
# callback
|
|
203
|
+
self._cached_readings = {}
|
|
204
|
+
for raw in self.raw_and_transform_subscribables.values():
|
|
205
|
+
raw.subscribe(self._update_cached_reading)
|
|
206
|
+
elif self._complete_cached_reading():
|
|
207
|
+
# Callback on the last complete set of readings
|
|
208
|
+
derived_readings = self._make_derived_readings(self._cached_readings)
|
|
209
|
+
callback(derived_readings[name])
|
|
210
|
+
|
|
211
|
+
async def get_locations(self) -> dict[str, Location]:
|
|
212
|
+
locations, transform = await asyncio.gather(
|
|
213
|
+
gather_dict({k: sig.locate() for k, sig in self.raw_locatables.items()}),
|
|
214
|
+
self.get_transform(),
|
|
215
|
+
)
|
|
216
|
+
raw_setpoints = {k: v["setpoint"] for k, v in locations.items()}
|
|
217
|
+
raw_readbacks = {k: v["readback"] for k, v in locations.items()}
|
|
218
|
+
derived_setpoints = transform.raw_to_derived(**raw_setpoints)
|
|
219
|
+
derived_readbacks = transform.raw_to_derived(**raw_readbacks)
|
|
220
|
+
return {
|
|
221
|
+
name: Location(
|
|
222
|
+
setpoint=derived_setpoints[name],
|
|
223
|
+
readback=derived_readbacks[name],
|
|
224
|
+
)
|
|
225
|
+
for name in derived_setpoints
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async def set_derived(self, name: str, value: Any):
|
|
229
|
+
if self._set_derived is None:
|
|
230
|
+
msg = "Cannot put as no set_derived method given"
|
|
231
|
+
raise RuntimeError(msg)
|
|
232
|
+
if self._need_dict:
|
|
233
|
+
# Need to get the other derived values and update the one that's changing
|
|
234
|
+
derived = await self.get_locations()
|
|
235
|
+
setpoints = {k: v["setpoint"] for k, v in derived.items()}
|
|
236
|
+
setpoints[name] = value
|
|
237
|
+
await self._set_derived(setpoints)
|
|
238
|
+
else:
|
|
239
|
+
# Only one derived signal, so pass it directly
|
|
240
|
+
await self._set_derived(value)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class DerivedSignalBackend(SignalBackend[SignalDatatypeT]):
|
|
244
|
+
def __init__(
|
|
245
|
+
self,
|
|
246
|
+
datatype: type[SignalDatatypeT],
|
|
247
|
+
name: str,
|
|
248
|
+
transformer: SignalTransformer,
|
|
249
|
+
units: str | None = None,
|
|
250
|
+
precision: int | None = None,
|
|
251
|
+
):
|
|
252
|
+
self.name = name
|
|
253
|
+
self.transformer = transformer
|
|
254
|
+
# Add the extra static metadata to the dictionary
|
|
255
|
+
self.metadata = make_metadata(datatype, units, precision)
|
|
256
|
+
super().__init__(datatype)
|
|
257
|
+
|
|
258
|
+
def source(self, name: str, read: bool) -> str:
|
|
259
|
+
return f"derived://{name}"
|
|
260
|
+
|
|
261
|
+
async def connect(self, timeout: float):
|
|
262
|
+
# Assume that the underlying signals are already connected
|
|
263
|
+
pass
|
|
264
|
+
|
|
265
|
+
def set_value(self, value: SignalDatatypeT):
|
|
266
|
+
msg = (
|
|
267
|
+
"Cannot set the value of a derived signal, "
|
|
268
|
+
"set the underlying raw signals instead"
|
|
269
|
+
)
|
|
270
|
+
raise RuntimeError(msg)
|
|
271
|
+
|
|
272
|
+
async def put(self, value: SignalDatatypeT | None, wait: bool) -> None:
|
|
273
|
+
if wait is False:
|
|
274
|
+
msg = "Cannot put with wait=False"
|
|
275
|
+
raise RuntimeError(msg)
|
|
276
|
+
if value is None:
|
|
277
|
+
msg = "Must be given a value to put"
|
|
278
|
+
raise RuntimeError(msg)
|
|
279
|
+
await self.transformer.set_derived(self.name, value)
|
|
280
|
+
|
|
281
|
+
async def get_datakey(self, source: str) -> DataKey:
|
|
282
|
+
return make_datakey(
|
|
283
|
+
self.datatype or float, await self.get_value(), source, self.metadata
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
async def get_reading(self) -> Reading[SignalDatatypeT]:
|
|
287
|
+
readings = await self.transformer.get_derived_readings()
|
|
288
|
+
return readings[self.name]
|
|
289
|
+
|
|
290
|
+
async def get_value(self) -> SignalDatatypeT:
|
|
291
|
+
derived = await self.transformer.get_derived_values()
|
|
292
|
+
return derived[self.name]
|
|
293
|
+
|
|
294
|
+
async def get_setpoint(self) -> SignalDatatypeT:
|
|
295
|
+
# TODO: should be get_location
|
|
296
|
+
locations = await self.transformer.get_locations()
|
|
297
|
+
return locations[self.name]["setpoint"]
|
|
298
|
+
|
|
299
|
+
def set_callback(self, callback: Callback[Reading[SignalDatatypeT]] | None) -> None:
|
|
300
|
+
self.transformer.set_callback(self.name, callback)
|