Skip to content

Commit 35cf5d4

Browse files
committed
Implement Waveform
1 parent 9ebdf4d commit 35cf5d4

6 files changed

Lines changed: 52 additions & 39 deletions

File tree

src/fastcs/datatypes/waveform.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def initial_value(self) -> np.ndarray:
2020
return np.zeros(self.shape, dtype=self.array_dtype)
2121

2222
def validate(self, value: np.ndarray) -> np.ndarray:
23-
_value = super().validate(value)
23+
_value = super().validate(np.asarray(value).astype(self.array_dtype))
2424

2525
if self.array_dtype != _value.dtype:
2626
raise ValueError(

src/fastcs/demo/controllers.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
from dataclasses import KW_ONLY, dataclass
55
from typing import TypeVar
66

7+
import numpy as np
8+
79
from fastcs.attributes import AttributeIO, AttributeIORef, AttrR, AttrRW, AttrW
810
from fastcs.connections import IPConnection, IPConnectionSettings
911
from fastcs.controllers import Controller
10-
from fastcs.datatypes import Enum, Float, Int
12+
from fastcs.datatypes import Enum, Float, Int, Waveform
1113
from fastcs.methods import command, scan
1214

1315
NumberT = TypeVar("NumberT", int, float)
@@ -66,6 +68,7 @@ async def update(
6668
class TemperatureController(Controller):
6769
ramp_rate = AttrRW(Float(), io_ref=TemperatureControllerAttributeIORef(name="R"))
6870
power = AttrR(Float(), io_ref=TemperatureControllerAttributeIORef(name="P"))
71+
voltages = AttrR(Waveform(np.int32, shape=(4,)))
6972

7073
def __init__(self, settings: TemperatureControllerSettings) -> None:
7174
self.connection = IPConnection()
@@ -101,6 +104,9 @@ async def update_voltages(self):
101104
voltages = json.loads(
102105
(await self.connection.send_query(f"{query}\r\n")).strip("\r\n")
103106
)
107+
108+
await self.voltages.update(voltages)
109+
104110
for index, controller in enumerate(self._ramp_controllers):
105111
self.log_event(
106112
"Update voltages",

src/fastcs/transports/epics/gui.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from pvi._format.dls import DLSFormatter # type: ignore
22
from pvi.device import (
33
LED,
4+
ArrayTrace,
45
ButtonPanel,
56
ComboBox,
67
ComponentUnion,
@@ -66,8 +67,12 @@ def _get_read_widget(self, fastcs_datatype: DataType) -> ReadWidgetUnion | None:
6667
return TextRead(format=TextFormat.string)
6768
case Enum():
6869
return TextRead(format=TextFormat.string)
69-
case Waveform():
70-
return None
70+
case Waveform() as waveform:
71+
if len(waveform.shape) > 1:
72+
logger.warning("EPICS CA transport only supports 1D waveforms")
73+
return None
74+
75+
return ArrayTrace(axis="x")
7176
case datatype:
7277
raise TypeError(f"Unsupported type {type(datatype)}: {datatype}")
7378

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from pvi.device import (
22
CheckBox,
3+
ImageRead,
34
ReadWidgetUnion,
45
TableRead,
56
TableWrite,
67
WriteWidgetUnion,
78
)
89

9-
from fastcs.datatypes import Bool, DataType, Table, numpy_to_fastcs_datatype
10+
from fastcs.datatypes import Bool, DataType, Table, Waveform, numpy_to_fastcs_datatype
1011
from fastcs.transports.epics.gui import EpicsGUI
1112

1213

@@ -18,31 +19,37 @@ class PvaEpicsGUI(EpicsGUI):
1819
def _get_pv(self, attr_path: list[str], name: str):
1920
return f"pva://{super()._get_pv(attr_path, name)}"
2021

21-
def _get_read_widget(self, fastcs_datatype: DataType) -> ReadWidgetUnion | None: # noqa: F821
22-
if isinstance(fastcs_datatype, Table):
23-
fastcs_datatypes = [
24-
numpy_to_fastcs_datatype(datatype)
25-
for _, datatype in fastcs_datatype.structured_dtype
26-
]
27-
28-
base_get_read_widget = super()._get_read_widget
29-
widgets = [base_get_read_widget(datatype) for datatype in fastcs_datatypes]
30-
31-
return TableRead(widgets=widgets) # type: ignore
32-
else:
33-
return super()._get_read_widget(fastcs_datatype)
22+
def _get_read_widget(self, fastcs_datatype: DataType) -> ReadWidgetUnion | None:
23+
match fastcs_datatype:
24+
case Table():
25+
fastcs_datatypes = [
26+
numpy_to_fastcs_datatype(datatype)
27+
for _, datatype in fastcs_datatype.structured_dtype
28+
]
29+
30+
base_get_read_widget = super()._get_read_widget
31+
widgets = [
32+
base_get_read_widget(datatype) for datatype in fastcs_datatypes
33+
]
34+
35+
return TableRead(widgets=widgets) # type: ignore
36+
case Waveform(shape=(height, width)):
37+
return ImageRead(height=height, width=width, grayscale=True)
38+
case _:
39+
return super()._get_read_widget(fastcs_datatype)
3440

3541
def _get_write_widget(self, fastcs_datatype: DataType) -> WriteWidgetUnion | None:
36-
if isinstance(fastcs_datatype, Table):
37-
widgets = []
38-
for _, datatype in fastcs_datatype.structured_dtype:
39-
fastcs_datatype = numpy_to_fastcs_datatype(datatype)
40-
if isinstance(fastcs_datatype, Bool):
41-
# Replace with compact version for Table row
42-
widget = CheckBox()
43-
else:
44-
widget = super()._get_write_widget(fastcs_datatype)
45-
widgets.append(widget)
46-
return TableWrite(widgets=widgets)
47-
else:
48-
return super()._get_write_widget(fastcs_datatype)
42+
match fastcs_datatype:
43+
case Table():
44+
widgets = []
45+
for _, datatype in fastcs_datatype.structured_dtype:
46+
fastcs_datatype = numpy_to_fastcs_datatype(datatype)
47+
if isinstance(fastcs_datatype, Bool):
48+
# Replace with compact version for Table row
49+
widget = CheckBox()
50+
else:
51+
widget = super()._get_write_widget(fastcs_datatype)
52+
widgets.append(widget)
53+
return TableWrite(widgets=widgets)
54+
case _:
55+
return super()._get_write_widget(fastcs_datatype)

tests/test_datatypes.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ class MyIntEnum(IntEnum):
3434
(Float, {"min": 1}, 0.0),
3535
(Float, {"max": -1}, 0.0),
3636
(Enum, {"enum_cls": int}, 0),
37-
(Waveform, {"array_dtype": "U64", "shape": (1,)}, np.ndarray([1])),
38-
(Waveform, {"array_dtype": "float64", "shape": (1, 1)}, np.ndarray([1])),
37+
(Waveform, {"array_dtype": "uint64", "shape": (1, 1)}, np.ndarray([1])),
3938
],
4039
)
4140
def test_validate(datatype, init_args, value):

tests/transports/epics/ca/test_gui.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import pytest
33
from pvi.device import (
44
LED,
5+
ArrayTrace,
56
ButtonPanel,
67
ComboBox,
78
Group,
@@ -39,7 +40,7 @@ def test_get_pv():
3940
(Float(), TextRead()),
4041
(String(), TextRead(format=TextFormat.string)),
4142
(Enum(ColourEnum), TextRead(format=TextFormat.string)),
42-
# (Waveform(array_dtype=np.int32), None),
43+
(Waveform(array_dtype=np.int32), ArrayTrace(axis="x")),
4344
],
4445
)
4546
def test_get_attribute_component_r(datatype, widget):
@@ -78,11 +79,6 @@ def test_get_attribute_component_none(mocker):
7879
assert gui._get_attribute_component([], "Attr", AttrRW(Int())) is None
7980

8081

81-
def test_get_read_widget_none():
82-
gui = EpicsGUI(ControllerAPI(), "DEVICE")
83-
assert gui._get_read_widget(fastcs_datatype=Waveform(np.int32)) is None
84-
85-
8682
def test_get_write_widget_none():
8783
gui = EpicsGUI(ControllerAPI(), "DEVICE")
8884
assert gui._get_write_widget(fastcs_datatype=Waveform(np.int32)) is None

0 commit comments

Comments
 (0)